diff --git a/.github/prquantifier.yaml b/.github/prquantifier.yaml index 240f660f88d..ea891ba4988 100644 --- a/.github/prquantifier.yaml +++ b/.github/prquantifier.yaml @@ -7,5 +7,5 @@ Excluded: - '*.md' - '*.sln' # autogenerated files -- cgmanifest.json +- tools/cgmanifest.json - assets/wix/files.wxs diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 55b7ba38f45..87ea7725cb9 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -72,14 +72,17 @@ jobs: git status --short - name: Check if we need to create a PR run: | + $ErrorActionPreference = 'continue' git diff --quiet ThirdPartyNotices.txt $exitCode = $LASTEXITCODE Write-Verbose -Message "Exit code: $exitCode" -Verbose if ($LASTEXITCODE -ne 0) { + Import-Module ./.github/workflows/GHWorkflowHelper Set-GWVariable -Name CREATE_PR -Value 'true' } else { Write-Verbose "No difference found. Not creating a PR." -Verbose } + exit 0 - name: Create Pull Request uses: peter-evans/create-pull-request@v3 id: cprtpn diff --git a/.spelling b/.spelling index e1347308644..a66b32d744b 100644 --- a/.spelling +++ b/.spelling @@ -15,6 +15,7 @@ about_ about_debuggers about_jobs +about_Telemetry acl adamdriscoll add-localgroupmember @@ -96,11 +97,14 @@ bugfix build.json build.psm1 bulid +buildInfoJson callmejoebob catchable cdxml celsius CentOS +cgmanifest +CGManifest changelog changelog.md changelogs @@ -115,6 +119,7 @@ childitem ChrisLGardner cimsession cimsupport +ci.psm1 classlib clear-itemproperty cloudydino @@ -233,6 +238,7 @@ DotnetRutimeMetadata.json dottedscopes downlevel dropdown +dwtaber e.g. ebook ebooks @@ -269,6 +275,7 @@ export-formatdata export-modulemember failurecode failurecount +farmerau fbehrens felixfbecker ffeldhaus @@ -352,6 +359,7 @@ hackathons HashSet hashtable hashtables +hayhay27 helloworld.ps1 helpproviderwithcache helpproviderwithfullcache @@ -369,6 +377,7 @@ httpbin's https hubuk hvitved +i3arnon i.e. ico idera @@ -426,8 +435,10 @@ jwmoss kanjibates kasper3 katacoda +Kellen-Stuart kevinmarquette kevinoid +KevRitchie keyfileparameter keyhandler khansen00 @@ -472,10 +483,12 @@ macports maertendmsft mahawar Markdig.Signed +markdown.yml markekraus marktiedemann Marusyk MarvTheRobot +matt9ucci mcbobke md meir017 @@ -631,6 +644,7 @@ powershellgallery powershellget powershellmagazine.com powershellninja +powershellpr0mpt powershellproperties ppadmavilasom pre-build @@ -722,6 +736,7 @@ receive-pssession recurse reddit redhat +redirections redistributable redistributables register-argumentcompleter @@ -769,6 +784,7 @@ rkitover robo210 ronn rpalo +rpolley runspace runspaceinit runspaces @@ -811,6 +827,7 @@ set-wsmaninstance set-wsmanquickconfig sethvs setversionvariables +sha256 ShaydeNofziger shellexecute shouldbeerrorid @@ -836,6 +853,7 @@ start-codecoveragerun start-pspester stdin stevel-msft +StevenLiekens stevend811 stknohg strawgate @@ -845,6 +863,7 @@ string.split stringbuilder stuntguy3000 StyleCop +subfolder submodule submodules sudo @@ -906,6 +925,7 @@ tracesource travisez13 travisty truher +TSAUpload TValue tylerleonhardt typecataloggen diff --git a/.vsts-ci/linux.yml b/.vsts-ci/linux.yml index e4ddeaf27f8..9faa155f74d 100644 --- a/.vsts-ci/linux.yml +++ b/.vsts-ci/linux.yml @@ -30,12 +30,14 @@ pr: - .vsts-ci/misc-analysis.yml - .vsts-ci/windows.yml - .vsts-ci/windows/* - - cgmanifest.json + - tools/cgmanifest.json - LICENSE.txt - test/common/markdown/* - test/perf/* - tools/releaseBuild/* - tools/releaseBuild/azureDevOps/templates/* + - README.md + - .spelling variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml index 84d3d1ef02a..c4623c9fef0 100644 --- a/.vsts-ci/mac.yml +++ b/.vsts-ci/mac.yml @@ -31,13 +31,15 @@ pr: - .vsts-ci/misc-analysis.yml - .vsts-ci/windows.yml - .vsts-ci/windows/* - - cgmanifest.json + - tools/cgmanifest.json - LICENSE.txt - test/common/markdown/* - test/perf/* - tools/packaging/* - tools/releaseBuild/* - tools/releaseBuild/azureDevOps/templates/* + - README.md + - .spelling variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 diff --git a/.vsts-ci/misc-analysis/markdown.yml b/.vsts-ci/misc-analysis/markdown.yml index 5266f90cb4d..b63e817ef35 100644 --- a/.vsts-ci/misc-analysis/markdown.yml +++ b/.vsts-ci/misc-analysis/markdown.yml @@ -30,12 +30,6 @@ jobs: - checkout: ComplianceRepo - - task: Cache@2 - inputs: - key: '"markdown-link-check" | "$(Agent.OS)" | $(repoPath)/test/common/markdown-link/markdown-link.tests.ps1' - path: $(YARN_GLOBAL_CACHE_FOLDER) - displayName: Cache Yarn packages - - powershell: | Install-module Pester -Scope CurrentUser -Force -MaximumVersion 4.99 displayName: Install Pester diff --git a/.vsts-ci/misc-analysis/mdSpell.yml b/.vsts-ci/misc-analysis/mdSpell.yml index e07b4756968..e9d046e5e96 100644 --- a/.vsts-ci/misc-analysis/mdSpell.yml +++ b/.vsts-ci/misc-analysis/mdSpell.yml @@ -53,4 +53,4 @@ jobs: - template: dailyBuildCompliance.yml@ComplianceRepo parameters: - sourceScanPath: '$(repoPath)' + sourceScanPath: '$(repoPath)/test/common' diff --git a/.vsts-ci/windows.yml b/.vsts-ci/windows.yml index 70775761fba..f886d2bd337 100644 --- a/.vsts-ci/windows.yml +++ b/.vsts-ci/windows.yml @@ -28,13 +28,15 @@ pr: - .dependabot/config.yml - .github/ISSUE_TEMPLATE/* - .vsts-ci/misc-analysis.yml - - cgmanifest.json + - tools/cgmanifest.json - LICENSE.txt - test/common/markdown/* - test/perf/* - tools/packaging/* - tools/releaseBuild/* - tools/releaseBuild/azureDevOps/templates/* + - README.md + - .spelling variables: GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" diff --git a/.vsts-ci/windows/templates/windows-packaging.yml b/.vsts-ci/windows/templates/windows-packaging.yml index 353d722bb92..88a557ca032 100644 --- a/.vsts-ci/windows/templates/windows-packaging.yml +++ b/.vsts-ci/windows/templates/windows-packaging.yml @@ -69,6 +69,7 @@ jobs: BuildDropPath: '$(System.ArtifactsDirectory)/mainBuild' Build_Repository_Uri: $(build.repository.uri) displayName: SBOM + sourceScanPath: '$(repoPath)\tools' - pwsh: | Import-Module .\tools\ci.psm1 diff --git a/.vsts-ci/windows/windows-packaging.yml b/.vsts-ci/windows/windows-packaging.yml index b8aa2c22950..b413b7a639b 100644 --- a/.vsts-ci/windows/windows-packaging.yml +++ b/.vsts-ci/windows/windows-packaging.yml @@ -51,6 +51,7 @@ pr: - src/Microsoft.WSMan.Runtime/Microsoft.WSMan.Runtime.csproj - src/Modules/PSGalleryModules.csproj - src/powershell-win-core/powershell-win-core.csproj + - test/packaging/windows/* - tools/ci.psm1 - tools/packaging/* diff --git a/CHANGELOG/6.0.md b/CHANGELOG/6.0.md index 948197f0619..52db53afabf 100644 --- a/CHANGELOG/6.0.md +++ b/CHANGELOG/6.0.md @@ -766,9 +766,8 @@ For more information on this, we invite you to read [this blog post explaining P ### Move to .NET Core 2.0 (.NET Standard 2.0 support) PowerShell Core has moved to using .NET Core 2.0 so that we can leverage all the benefits of .NET Standard 2.0. (#3556) -To learn more about .NET Standard 2.0, there's some great starter content [on Youtube](https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY), -on [the .NET blog](https://devblogs.microsoft.com/dotnet/introducing-net-standard/), -and [on GitHub](https://github.com/dotnet/standard/blob/master/docs/faq.md). +To learn more about .NET Standard 2.0, there's some great starter content [on Youtube](https://www.youtube.com/playlist?list=PLRAdsfhKI4OWx321A_pr-7HhRNk7wOLLY) +and on [the .NET blog](https://devblogs.microsoft.com/dotnet/introducing-net-standard/). We'll also have more content soon in our [repository documentation](https://github.com/PowerShell/PowerShell/tree/master/docs) (which will eventually make its way to [official documentation](https://github.com/powershell/powershell-docs)). In a nutshell, .NET Standard 2.0 allows us to have universal, portable modules between Windows PowerShell (which uses the full .NET Framework) and PowerShell Core (which uses .NET Core). Many modules and cmdlets that didn't work in the past may now work on .NET Core, so import your favorite modules and tell us what does and doesn't work in our GitHub Issues! diff --git a/CHANGELOG/7.2.md b/CHANGELOG/7.2.md index 0a570847848..8eb70874ef5 100644 --- a/CHANGELOG/7.2.md +++ b/CHANGELOG/7.2.md @@ -1,5 +1,44 @@ # 7.2 Changelog +## [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 + +
+ + + +
+ +[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 diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md index 83efcb0fed5..60ae3af38c4 100644 --- a/CHANGELOG/README.md +++ b/CHANGELOG/README.md @@ -1,6 +1,7 @@ # Changelogs * [Current preview changelog](preview.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) diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index 035db721385..799630aa599 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1 +1,230 @@ # Current preview release + +## [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

+ +
+ + + +
+ +### 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

+ +
+ + + +
+ +### 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 change logs (#16471) (Thanks @powershellpr0mpt!) +- Update change log 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 change logs 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/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index 1b95abccd68..96adaddc003 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -6,10 +6,10 @@ "packageVersionPattern": "6.0.0", "sdkImageVersion": "6.0.100", "nextChannel": "6.0.1xx", - "azureFeed": "https://dotnetstage.blob.core.windows.net/6-0-100-rtm-21527-11-public", - "sdkImageOverride": "6.0.100-rtm.21527.11" + "azureFeed": "", + "sdkImageOverride": "" }, "internalfeed" : { - "url": "https://pkgs.dev.azure.com/dnceng/public/_packaging/6.0.100-rtm.21527.11-shipping/nuget/v2" + "url": "" } } diff --git a/README.md b/README.md index ad198f4feea..0c0163fc0f5 100644 --- a/README.md +++ b/README.md @@ -64,49 +64,49 @@ You can also download the PowerShell binary archives for Windows, macOS and Linu | Windows (Arm) | [64-bit][rl-winarm64] (preview) | [64-bit][pv-winarm64] | [Instructions][in-arm] | | Raspbian (Arm) | [32-bit][rl-arm32]/[64-bit][rl-arm64] | [32-bit][pv-arm32]/[64-bit][pv-arm64] | [Instructions][in-raspbian] | -[lts-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-x86.msi -[lts-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-x64.msi -[lts-deb]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-lts_7.2.0-1.deb_amd64.deb -[lts-rh]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-lts-7.2.0-1.rh.x86_64.rpm -[lts-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-lts-7.2.0-osx-x64.pkg -[lts-macos-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-lts-7.2.0-osx-arm64.pkg - -[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-x64.msi -[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-x86.msi -[rl-ubuntu20]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell_7.2.0-1.deb_amd64.deb -[rl-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell_7.2.0-1.deb_amd64.deb -[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell_7.2.0-1.deb_amd64.deb -[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell_7.2.0-1.deb_amd64.deb -[rl-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell_7.2.0-1.deb_amd64.deb -[rl-debian11]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell_7.2.0-1.deb_amd64.deb -[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-1.rh.x86_64.rpm -[rl-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-1.rh.x86_64.rpm -[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-osx-x64.pkg -[rl-macos-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-osx-arm64.pkg -[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-arm64.zip -[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-x86.zip -[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/PowerShell-7.2.0-win-x64.zip -[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-osx-x64.tar.gz -[rl-macos-tar-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-osx-arm64.tar.gz -[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-linux-x64.tar.gz -[rl-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-linux-arm32.tar.gz -[rl-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0/powershell-7.2.0-linux-arm64.tar.gz +[lts-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x86.msi +[lts-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x64.msi +[lts-deb]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-lts_7.2.1-1.deb_amd64.deb +[lts-rh]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-lts-7.2.1-1.rh.x86_64.rpm +[lts-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-lts-7.2.1-osx-x64.pkg +[lts-macos-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-lts-7.2.1-osx-arm64.pkg + +[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x64.msi +[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x86.msi +[rl-ubuntu20]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell_7.2.1-1.deb_amd64.deb +[rl-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell_7.2.1-1.deb_amd64.deb +[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell_7.2.1-1.deb_amd64.deb +[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell_7.2.1-1.deb_amd64.deb +[rl-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell_7.2.1-1.deb_amd64.deb +[rl-debian11]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell_7.2.1-1.deb_amd64.deb +[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-1.rh.x86_64.rpm +[rl-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-1.rh.x86_64.rpm +[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-osx-x64.pkg +[rl-macos-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-osx-arm64.pkg +[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-arm64.zip +[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x86.zip +[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/PowerShell-7.2.1-win-x64.zip +[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-osx-x64.tar.gz +[rl-macos-tar-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-osx-arm64.tar.gz +[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-linux-x64.tar.gz +[rl-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-linux-arm32.tar.gz +[rl-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.1/powershell-7.2.1-linux-arm64.tar.gz [rl-snap]: https://snapcraft.io/powershell -[pv-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/PowerShell-7.2.0-rc.1-win-x64.msi -[pv-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/PowerShell-7.2.0-rc.1-win-x86.msi -[pv-deb]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-preview_7.2.0-rc.1-1.deb_amd64.deb -[pv-rpm]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-preview-7.2.0_rc.1-1.rh.x86_64.rpm -[pv-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-osx-x64.pkg -[pv-macos-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-osx-arm64.pkg -[pv-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/PowerShell-7.2.0-rc.1-win-arm64.zip -[pv-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/PowerShell-7.2.0-rc.1-win-x86.zip -[pv-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/PowerShell-7.2.0-rc.1-win-x64.zip -[pv-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-osx-x64.tar.gz -[pv-macos-tar-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-osx-arm64.tar.gz -[pv-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-linux-x64.tar.gz -[pv-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-linux-arm32.tar.gz -[pv-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.2.0-rc.1/powershell-7.2.0-rc.1-linux-arm64.tar.gz +[pv-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/PowerShell-7.3.0-preview.1-win-x64.msi +[pv-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/PowerShell-7.3.0-preview.1-win-x86.msi +[pv-deb]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-preview_7.3.0-preview.1-1.deb_amd64.deb +[pv-rpm]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-preview-7.3.0_preview.1-1.rh.x86_64.rpm +[pv-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-osx-x64.pkg +[pv-macos-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-osx-arm64.pkg +[pv-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/PowerShell-7.3.0-preview.1-win-arm64.zip +[pv-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/PowerShell-7.3.0-preview.1-win-x86.zip +[pv-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/PowerShell-7.3.0-preview.1-win-x64.zip +[pv-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-osx-x64.tar.gz +[pv-macos-tar-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-osx-arm64.tar.gz +[pv-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-linux-x64.tar.gz +[pv-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-linux-arm32.tar.gz +[pv-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.3.0-preview.1/powershell-7.3.0-preview.1-linux-arm64.tar.gz [pv-snap]: https://snapcraft.io/powershell-preview [in-windows]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-windows @@ -239,9 +239,8 @@ License: By requesting and using the Container OS Image for Windows containers, ### Telemetry -By default, PowerShell collects the OS description and the version of PowerShell (equivalent to `$PSVersionTable.OS` and `$PSVersionTable.GitCommitId`) using [Application Insights](https://azure.microsoft.com/services/application-insights/). -To opt-out of sending telemetry, create an environment variable called `POWERSHELL_TELEMETRY_OPTOUT` set to a value of `1` before starting PowerShell from the installed location. -The telemetry we collect falls under the [Microsoft Privacy Statement](https://privacy.microsoft.com/privacystatement/). +Please visit our [about_Telemetry](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_telemetry) +topic to read details about telemetry gathered by PowerShell. ## Governance diff --git a/assets/wix/Product.wxs b/assets/wix/Product.wxs index 3691f5268ca..bf6ab5d7cd8 100644 --- a/assets/wix/Product.wxs +++ b/assets/wix/Product.wxs @@ -361,7 +361,7 @@ See the Microsoft Update FAQ]]> - Read the Microsoft Update Priacy Statement]]> + Read the Microsoft Update Privacy Statement]]> diff --git a/assets/wix/files.wxs b/assets/wix/files.wxs index 13e38dbdd68..78b21b8d643 100644 --- a/assets/wix/files.wxs +++ b/assets/wix/files.wxs @@ -3137,6 +3137,9 @@ + + + @@ -4170,6 +4173,7 @@ + diff --git a/build.psm1 b/build.psm1 index 2900e1bc4f8..4330011863e 100644 --- a/build.psm1 +++ b/build.psm1 @@ -439,10 +439,9 @@ Fix steps: # 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" - # The UseAppHost = false property avoids creating ".exe" for the fxdependent packages. - # The ".exe" is not a cross-platform executable, but specific to the platform that it was built on. - # We do not need to ship that. - $Arguments += "/property:UseAppHost=false" + # The UseAppHost = true property creates ".exe" for the fxdependent packages. + # We need this in the package as Start-Job needs it. + $Arguments += "/property:UseAppHost=true" } else { $Arguments += "--self-contained" @@ -1855,7 +1854,7 @@ function Install-Dotnet { 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 + $fullDotnetInstallPath = Join-Path -Path (Convert-Path -Path $PWD.Path) -ChildPath $installScript if ($Version) { $psArgs = @('-NoLogo', '-NoProfile', '-File', $fullDotnetInstallPath, '-Version', $Version, '-Quality', $Quality) @@ -2239,7 +2238,7 @@ function Start-ResGen } function Find-Dotnet() { - Write-Verbose -Verbose "In Find-DotNet" + Write-Verbose "In Find-DotNet" $originalPath = $env:PATH $dotnetPath = if ($environment.IsWindows) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" } @@ -2259,7 +2258,7 @@ function Find-Dotnet() { $dotnetCLIInstalledVersion = Get-LatestInstalledSDK Pop-Location - Write-Verbose -Verbose "dotnetCLIInstalledVersion = $dotnetCLIInstalledVersion`nchosenDotNetVersion = $chosenDotNetVersion" + Write-Verbose "dotnetCLIInstalledVersion = $dotnetCLIInstalledVersion; chosenDotNetVersion = $chosenDotNetVersion" if ($dotnetCLIInstalledVersion -ne $chosenDotNetVersion) { Write-Warning "The 'dotnet' in the current path can't find SDK version ${dotnetCLIRequiredVersion}, prepending $dotnetPath to PATH." diff --git a/demos/DSC/readme.md b/demos/DSC/readme.md index 0ce83fb3efa..0dd98968d2f 100644 --- a/demos/DSC/readme.md +++ b/demos/DSC/readme.md @@ -1,6 +1,6 @@ # DSC MOF Compilation Demo -[PowerShell Desired State Configuration](https://docs.microsoft.com/powershell/scripting/dsc/overview/overview) is a declarative configuration platform for Windows and Linux. +[PowerShell Desired State Configuration](https://docs.microsoft.com/powershell/scripting/dsc/overview) is a declarative configuration platform for Windows and Linux. DSC configurations can be authored in PowerShell and compiled into the resultant MOF document. This demo shows use of PowerShell to author a DSC configuration to set the configuration of an Apache web server. PowerShell scripting is used to assess distribution and version-specific properties, diff --git a/docs/dev-process/coding-guidelines.md b/docs/dev-process/coding-guidelines.md index 9bf46aee02d..003321b9f85 100644 --- a/docs/dev-process/coding-guidelines.md +++ b/docs/dev-process/coding-guidelines.md @@ -88,7 +88,7 @@ We also run the [.NET code formatter tool](https://github.com/dotnet/codeformatt ### Documentation comments -* Create documentation using [XML documentation comments](https://docs.microsoft.com/dotnet/csharp/codedoc) so that Visual Studio and other IDEs can use IntelliSense to show quick information about types or members. +* Create documentation using [XML documentation comments](https://docs.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. @@ -193,7 +193,7 @@ See [CODEOWNERS](../../.github/CODEOWNERS) for more information about the area e * [Arrays](https://docs.microsoft.com/dotnet/standard/design-guidelines/arrays) * [Collections](https://docs.microsoft.com/dotnet/standard/design-guidelines/guidelines-for-collections) * [Exceptions](https://docs.microsoft.com/dotnet/standard/design-guidelines/exceptions) - * [Best Practices for Developing World-Ready Applications](https://docs.microsoft.com/dotnet/standard/globalization-localization/best-practices-for-developing-world-ready-apps) - Unicode, Culture, Encoding and Localization. + * [Best Practices for Developing World-Ready Applications](https://docs.microsoft.com/dotnet/core/extensions/best-practices-for-developing-world-ready-apps) - Unicode, Culture, Encoding and Localization. * [Best Practices for Exceptions](https://docs.microsoft.com/dotnet/standard/exceptions/best-practices-for-exceptions) * [Best Practices for Using Strings in .NET](https://docs.microsoft.com/dotnet/standard/base-types/best-practices-strings) * [Best Practices for Regular Expressions in .NET](https://docs.microsoft.com/dotnet/standard/base-types/best-practices) diff --git a/nuget.config b/nuget.config index cf9fec6fb1e..5a541e6ff31 100644 --- a/nuget.config +++ b/nuget.config @@ -2,9 +2,7 @@ - - diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index bff5cc802d0..bd73e6c005b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -31,7 +31,7 @@ - + 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 e6a99c38cb0..d885d0a64f1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -792,9 +792,9 @@ private void WritePropertyNotFoundError(string propertyName, string errorId) { Diagnostics.Assert(Property != null, "no property and no InputObject should have been addressed"); ErrorRecord errorRecord = new( - PSTraceSource.NewArgumentException("Property"), + PSTraceSource.NewArgumentException(propertyName), errorId, - ErrorCategory.InvalidArgument, + ErrorCategory.ObjectNotFound, null); errorRecord.ErrorDetails = new ErrorDetails( this, "MeasureObjectStrings", "PropertyNotFound", propertyName); @@ -820,9 +820,12 @@ protected override void EndProcessing() Statistics stat = _statistics[propertyName]; if (stat.count == 0 && Property != null) { - // Why are there two different ids for this error? - string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound"; - WritePropertyNotFoundError(propertyName, errorId); + if (Context.IsStrictVersion(2)) + { + string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound"; + WritePropertyNotFoundError(propertyName, errorId); + } + continue; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index a57d635f3eb..a1524e5ed4f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -55,6 +55,15 @@ public void Dispose() [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 @@ -104,6 +113,26 @@ protected override void ProcessRecord() case "Milliseconds": sleepTime = Milliseconds; break; + + case "FromTimeSpan": + if (Duration.TotalMilliseconds > int.MaxValue) + { + PSArgumentException argumentException = PSTraceSource.NewArgumentException( + nameof(Duration), + StartSleepStrings.MaximumDurationExceeded, + TimeSpan.FromMilliseconds(int.MaxValue), + Duration); + + ThrowTerminatingError( + new ErrorRecord( + argumentException, + "MaximumDurationExceeded", + ErrorCategory.InvalidArgument, + targetObject: null)); + } + + sleepTime = (int)Math.Floor(Duration.TotalMilliseconds); + break; default: Dbg.Diagnostics.Assert(false, "Only one of the specified parameter sets should be called."); 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 a5a4060e7fd..30c55f506f0 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 @@ -1277,9 +1277,15 @@ internal virtual void FillRequestStream(HttpRequestMessage request) } } - // Add the content headers - if (request.Content == null) + // For other methods like Put where empty content has meaning, we need to fill in the content + if (request.Content is null) { + // If this is a Get request and there is no content, then don't fill in the content as empty content gets rejected by some web services per RFC7230 + if ((IsStandardMethodSet() && request.Method == HttpMethod.Get && ContentType is null) || (IsCustomMethodSet() && CustomMethod.ToUpperInvariant() == "GET")) + { + return; + } + request.Content = new StringContent(string.Empty); request.Content.Headers.Clear(); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx new file mode 100644 index 00000000000..32804b9e21b --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/StartSleepStrings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The '-Duration' parameter value must not exceed '{0}', provided value was '{1}'. + + diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index 6908320db53..795921a0c5a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -690,7 +690,6 @@ private void DisplayBanner(PSHostUserInterface hostUI, string? bannerText) if (!string.IsNullOrEmpty(bannerText)) { hostUI.WriteLine(bannerText); - hostUI.WriteLine(); } if (UpdatesNotification.CanNotifyUpdates) diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index c2206ac7de8..4703d54d880 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -16,6 +16,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Management.Automation.Remoting; +using System.Management.Automation.Remoting.Server; using System.Management.Automation.Runspaces; using System.Management.Automation.Tracing; using System.Reflection; @@ -193,7 +194,7 @@ internal static int Start(string bannerText, string helpText) { ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode"); ProfileOptimization.StartProfile("StartupProfileData-ServerMode"); - System.Management.Automation.Remoting.Server.StdIOProcessMediator.Run( + StdIOProcessMediator.Run( initialCommand: s_cpp.InitialCommand, workingDirectory: s_cpp.WorkingDirectory, configurationName: null); @@ -203,7 +204,7 @@ internal static int Start(string bannerText, string helpText) { ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer"); ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode"); - System.Management.Automation.Remoting.Server.StdIOProcessMediator.Run( + StdIOProcessMediator.Run( initialCommand: s_cpp.InitialCommand, workingDirectory: null, configurationName: null); @@ -213,7 +214,7 @@ internal static int Start(string bannerText, string helpText) { ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe"); ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode"); - System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode( + RemoteSessionNamedPipeServer.RunServerMode( configurationName: s_cpp.ConfigurationName); exitCode = 0; } @@ -221,7 +222,7 @@ internal static int Start(string bannerText, string helpText) { ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode"); ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode"); - System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run( + HyperVSocketMediator.Run( initialCommand: s_cpp.InitialCommand, configurationName: s_cpp.ConfigurationName); exitCode = 0; @@ -1070,18 +1071,16 @@ public override void NotifyBeginApplication() { lock (hostGlobalLock) { - ++_beginApplicationNotifyCount; - if (_beginApplicationNotifyCount == 1) + if (++_beginApplicationNotifyCount == 1) { - // save the window title when first notified. - + // Save the window title when first notified. _savedWindowTitle = ui.RawUI.WindowTitle; #if !UNIX if (_initialConsoleMode != ConsoleControl.ConsoleModes.Unknown) { - var activeScreenBufferHandle = ConsoleControl.GetActiveScreenBufferHandle(); - _savedConsoleMode = ConsoleControl.GetMode(activeScreenBufferHandle); - ConsoleControl.SetMode(activeScreenBufferHandle, _initialConsoleMode); + var outputHandle = ConsoleControl.GetActiveScreenBufferHandle(); + _savedConsoleMode = ConsoleControl.GetMode(outputHandle); + ConsoleControl.SetMode(outputHandle, _initialConsoleMode); } #endif } @@ -1096,17 +1095,26 @@ public override void NotifyEndApplication() { lock (hostGlobalLock) { - Dbg.Assert(_beginApplicationNotifyCount > 0, "Not running an executable - NotifyBeginApplication was not called!"); - --_beginApplicationNotifyCount; - if (_beginApplicationNotifyCount == 0) + if (--_beginApplicationNotifyCount == 0) { - // restore the window title when the last application started has ended. - + // Restore the window title when the last application started has ended. ui.RawUI.WindowTitle = _savedWindowTitle; #if !UNIX if (_savedConsoleMode != ConsoleControl.ConsoleModes.Unknown) { ConsoleControl.SetMode(ConsoleControl.GetActiveScreenBufferHandle(), _savedConsoleMode); + if (_savedConsoleMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal)) + { + // If the console output mode we just set already has 'VirtualTerminal' turned on, + // we don't need to try turn on the VT mode separately. + return; + } + } + + if (ui.SupportsVirtualTerminal) + { + // Re-enable VT mode if it was previously enabled, as a native command may have turned it off. + ui.TryTurnOnVirtualTerminal(); } #endif } @@ -2451,14 +2459,6 @@ internal void Run(bool inputLoopIsNested) while (!_parent.ShouldEndSession && !_shouldExit) { -#if !UNIX - if (ui.SupportsVirtualTerminal) - { - // need to re-enable VT mode if it was previously enabled as native commands may have turned it off - ui.TryTurnOnVtMode(); - } -#endif - try { _parent._isRunningPromptLoop = true; diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index ad20eadf3d8..98abed42576 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -87,11 +87,11 @@ internal ConsoleHostUserInterface(ConsoleHost parent) if (SupportsVirtualTerminal) { - SupportsVirtualTerminal = TryTurnOnVtMode(); + SupportsVirtualTerminal = TryTurnOnVirtualTerminal(); } } - internal bool TryTurnOnVtMode() + internal bool TryTurnOnVirtualTerminal() { #if UNIX return true; @@ -99,16 +99,22 @@ internal bool TryTurnOnVtMode() try { // Turn on virtual terminal if possible. - // This might throw - not sure how exactly (no console), but if it does, we shouldn't fail to start. - var handle = ConsoleControl.GetActiveScreenBufferHandle(); - var m = ConsoleControl.GetMode(handle); - if (ConsoleControl.NativeMethods.SetConsoleMode(handle.DangerousGetHandle(), (uint)(m | ConsoleControl.ConsoleModes.VirtualTerminal))) + var outputHandle = ConsoleControl.GetActiveScreenBufferHandle(); + var outputMode = ConsoleControl.GetMode(outputHandle); + + if (outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal)) + { + return true; + } + + outputMode |= ConsoleControl.ConsoleModes.VirtualTerminal; + if (ConsoleControl.NativeMethods.SetConsoleMode(outputHandle.DangerousGetHandle(), (uint)outputMode)) { // We only know if vt100 is supported if the previous call actually set the new flag, older // systems ignore the setting. - m = ConsoleControl.GetMode(handle); - return (m & ConsoleControl.ConsoleModes.VirtualTerminal) != 0; + outputMode = ConsoleControl.GetMode(outputHandle); + return outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal); } } catch @@ -729,7 +735,7 @@ private void WriteImpl(string value, bool newLine) } TextWriter writer = Console.IsOutputRedirected ? Console.Out : _parent.ConsoleTextWriter; - value = GetOutputString(value, SupportsVirtualTerminal, Console.IsOutputRedirected); + value = GetOutputString(value, SupportsVirtualTerminal); if (_parent.IsRunningAsync) { @@ -1215,7 +1221,7 @@ public override void WriteDebugLine(string message) { if (SupportsVirtualTerminal) { - WriteLine(GetFormatStyleString(FormatStyle.Debug, Console.IsOutputRedirected) + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message) + PSStyle.Instance.Reset); + WriteLine(GetFormatStyleString(FormatStyle.Debug) + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message) + PSStyle.Instance.Reset); } else { @@ -1276,7 +1282,7 @@ public override void WriteVerboseLine(string message) { if (SupportsVirtualTerminal) { - WriteLine(GetFormatStyleString(FormatStyle.Verbose, Console.IsOutputRedirected) + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message) + PSStyle.Instance.Reset); + WriteLine(GetFormatStyleString(FormatStyle.Verbose) + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message) + PSStyle.Instance.Reset); } else { @@ -1320,7 +1326,7 @@ public override void WriteWarningLine(string message) { if (SupportsVirtualTerminal) { - WriteLine(GetFormatStyleString(FormatStyle.Warning, Console.IsOutputRedirected) + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message) + PSStyle.Instance.Reset); + WriteLine(GetFormatStyleString(FormatStyle.Warning) + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message) + PSStyle.Instance.Reset); } else { diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs index fad667dba2d..df120a2d0e5 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs @@ -937,8 +937,6 @@ internal override return nodesCompressed; } - Dbg.Assert(false, "with all nodes invisible, we should never reach this point."); - return 0; } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs index dff3757259f..455efe84d11 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -108,7 +108,7 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) // We calculate how much whitespace we need to make it look nice if (hostUI.SupportsVirtualTerminal) { - // Use Warning Color + // Swaps foreground and background colors. notificationColor = "\x1B[7m"; resetColor = "\x1B[0m"; @@ -126,6 +126,7 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) string notificationMsg = string.Format(CultureInfo.CurrentCulture, notificationMsgTemplate, releaseTag, notificationColor, resetColor, line2Padding, line3Padding); + hostUI.WriteLine(); hostUI.WriteLine(notificationMsg); } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index 7b13837b294..481a4216cf9 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -118,11 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - PowerShell {0} -Copyright (c) Microsoft Corporation. - -https://aka.ms/powershell -Type 'help' to get help. + PowerShell {0} Warning: PowerShell detected that you might be using a screen reader and has disabled PSReadLine for compatibility purposes. If you want to re-enable it, run 'Import-Module PSReadLine'. @@ -383,7 +379,7 @@ All parameters are case-insensitive. -NoLogo | -nol - Hides the copyright banner at startup of interactive sessions. + Hides the banner text at startup of interactive sessions. -NonInteractive | -noni diff --git a/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 b/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 index 0270ceffca0..e6d616aa61d 100644 --- a/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 +++ b/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 @@ -10,5 +10,5 @@ FunctionsToExport = @() CmdletsToExport="Start-Transcript", "Stop-Transcript" AliasesToExport = @() NestedModules="Microsoft.PowerShell.ConsoleHost.dll" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 index da8f8707945..6eb576cdf03 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -7,7 +7,7 @@ ModuleVersion="7.0.0.0" CompatiblePSEditions = @("Core") PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' FunctionsToExport = @() AliasesToExport = @("gcb", "gtz", "scb") CmdletsToExport=@("Add-Content", diff --git a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 index 1f4cc15e118..440153be545 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -10,5 +10,5 @@ FunctionsToExport = @() CmdletsToExport="Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-PfxCertificate" , "Protect-CmsMessage", "Unprotect-CmsMessage", "Get-CmsMessage" AliasesToExport = @() NestedModules="Microsoft.PowerShell.Security.dll" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index b883acdd0d6..5761b3c745a 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -30,19 +30,5 @@ CmdletsToExport = @( FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") -HelpInfoURI = 'https://aka.ms/powershell71-help' -PrivateData = @{ - PSData = @{ - ExperimentalFeatures = @( - @{ - Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' - Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger. Enables -Runspace parameter on *-PSBreakpoint cmdlets to support management of breakpoints in another runspace.' - } - @{ - Name = 'Microsoft.PowerShell.Utility.PSImportPSDataFileSkipLimitCheck' - Description = 'Enable -SkipLimitCheck switch for Import-PowerShellDataFile to not enforce built-in hashtable limits' - } - ) - } -} +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 index 4b38d4abc9e..93c68321ca1 100644 --- a/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 +++ b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 @@ -14,5 +14,5 @@ CmdletsToExport= "Get-CimAssociatedInstance", "Get-CimClass", "Get-CimInstance", "Remove-CimSession","Set-CimInstance", "Export-BinaryMiLog","Import-BinaryMiLog" AliasesToExport = "gcim","scim","ncim", "rcim","icim","gcai","rcie","ncms","rcms","gcms","ncso","gcls" -HelpInfoUri="https://aka.ms/powershell71-help" +HelpInfoUri="https://aka.ms/powershell73-help" } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 index ed2344b51b2..50282d8d5b8 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 @@ -12,5 +12,5 @@ AliasesToExport = @() NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" TypesToProcess="GetEvent.types.ps1xml" FormatsToProcess="Event.format.ps1xml", "Diagnostics.format.ps1xml" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 index f7cd1dc6ace..0b49f178b25 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -7,7 +7,7 @@ ModuleVersion="7.0.0.0" CompatiblePSEditions = @("Core") PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' FunctionsToExport = @() AliasesToExport = @("gcb", "gin", "gtz", "scb", "stz") CmdletsToExport=@("Add-Content", diff --git a/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 index cbc5b2dc78e..b88bc92d2a2 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -10,5 +10,5 @@ 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" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 5ea504197fa..7d0e7a91e11 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -29,19 +29,5 @@ CmdletsToExport = @( FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") -HelpInfoURI = 'https://aka.ms/powershell71-help' -PrivateData = @{ - PSData = @{ - ExperimentalFeatures = @( - @{ - Name = 'Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace' - Description = 'Enables -BreakAll parameter on Debug-Runspace and Debug-Job cmdlets to allow users to decide if they want PowerShell to break immediately in the current location when they attach a debugger. Enables -Runspace parameter on *-PSBreakpoint cmdlets to support management of breakpoints in another runspace.' - } - @{ - Name = 'Microsoft.PowerShell.Utility.PSImportPSDataFileSkipLimitCheck' - Description = 'Enable -NoLimit switch for Import-PowerShellDataFile to not enforce built-in hashtable limits' - } - ) - } -} +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 b/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 index d2bb2398541..5eb367b7e7f 100644 --- a/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 +++ b/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 @@ -11,5 +11,5 @@ CmdletsToExport="Disable-WSManCredSSP", "Enable-WSManCredSSP", "Get-WSManCredSSP AliasesToExport = @() NestedModules="Microsoft.WSMan.Management.dll" FormatsToProcess="WSMan.format.ps1xml" -HelpInfoURI = 'https://aka.ms/powershell71-help' +HelpInfoURI = 'https://aka.ms/powershell73-help' } diff --git a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 index dded04d4920..6185b589a82 100644 --- a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 +++ b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 @@ -10,5 +10,5 @@ FunctionsToExport="Disable-PSTrace","Disable-PSWSManCombinedTrace","Disable-WSManTrace","Enable-PSTrace","Enable-PSWSManCombinedTrace","Enable-WSManTrace","Get-LogProperties","Set-LogProperties","Start-Trace","Stop-Trace" CmdletsToExport = @() AliasesToExport = @() - HelpInfoUri="https://aka.ms/powershell71-help" + HelpInfoUri="https://aka.ms/powershell73-help" } diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index dd0877710d6..45be8721b7f 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -5,9 +5,8 @@ using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; - +using System.Management.Automation.Internal; using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; namespace System.Management.Automation { @@ -161,6 +160,7 @@ public static bool IsStaSupported // Gets the location for cache and config folders. internal static readonly string CacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell"; internal static readonly string ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"\PowerShell"; + private static readonly Lazy _isStaSupported = new Lazy(() => { // See objbase.h @@ -199,8 +199,6 @@ public static bool IsStaSupported "WSMan.format.ps1xml" }; - private static string _tempDirectory = null; - /// /// Some common environment variables used in PS have different /// names in different OS platforms. @@ -214,43 +212,37 @@ internal static class CommonEnvVariableNames #endif } +#if UNIX + private static string s_tempHome = null; + /// - /// Remove the temporary directory created for the current process. + /// Get the 'HOME' environment variable or create a temporary home diretory if the environment variable is not set. /// - internal static void RemoveTemporaryDirectory() + private static string GetHomeOrCreateTempHome() { - if (_tempDirectory == null) + const string tempHomeFolderName = "pwsh-{0}-98288ff9-5712-4a14-9a11-23693b9cd91a"; + + string envHome = Environment.GetEnvironmentVariable("HOME") ?? s_tempHome; + if (envHome is not null) { - return; + return envHome; } try { - Directory.Delete(_tempDirectory, true); + s_tempHome = Path.Combine(Path.GetTempPath(), StringUtil.Format(tempHomeFolderName, Environment.UserName)); + Directory.CreateDirectory(s_tempHome); } - catch - { - // ignore if there is a failure - } - - _tempDirectory = null; - } - - /// - /// Get a temporary directory to use for the current process. - /// - internal static string GetTemporaryDirectory() - { - if (_tempDirectory != null) + catch (UnauthorizedAccessException) { - return _tempDirectory; + // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts. + // Return an empty string in this case so the process working directory will be used. + s_tempHome = string.Empty; } - _tempDirectory = PsUtils.GetTemporaryDirectory(); - return _tempDirectory; + return s_tempHome; } -#if UNIX /// /// X Desktop Group configuration type enum. /// @@ -270,230 +262,100 @@ public enum XDG_Type DEFAULT } - private static string s_tempHomeDir = null; - /// /// Function for choosing directory location of PowerShell for profile loading. /// - public static string SelectProductNameForDirectory(Platform.XDG_Type dirpath) + public static string SelectProductNameForDirectory(XDG_Type dirpath) { // TODO: XDG_DATA_DIRS implementation as per GitHub issue #1060 - string xdgconfighome = System.Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); - string xdgdatahome = System.Environment.GetEnvironmentVariable("XDG_DATA_HOME"); - string xdgcachehome = System.Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); - string envHome = System.Environment.GetEnvironmentVariable(CommonEnvVariableNames.Home); - if (envHome == null) - { - s_tempHomeDir ??= GetTemporaryDirectory(); - envHome = s_tempHomeDir; - } + string xdgconfighome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); + string xdgdatahome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); + string xdgcachehome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); + string envHome = GetHomeOrCreateTempHome(); string xdgConfigHomeDefault = Path.Combine(envHome, ".config", "powershell"); string xdgDataHomeDefault = Path.Combine(envHome, ".local", "share", "powershell"); string xdgModuleDefault = Path.Combine(xdgDataHomeDefault, "Modules"); string xdgCacheDefault = Path.Combine(envHome, ".cache", "powershell"); - switch (dirpath) + try { - case Platform.XDG_Type.CONFIG: - // the user has set XDG_CONFIG_HOME corresponding to profile path - if (string.IsNullOrEmpty(xdgconfighome)) - { - // xdg values have not been set - return xdgConfigHomeDefault; - } - - else - { - return Path.Combine(xdgconfighome, "powershell"); - } - - case Platform.XDG_Type.DATA: - // the user has set XDG_DATA_HOME corresponding to module path - if (string.IsNullOrEmpty(xdgdatahome)) - { - // create the xdg folder if needed - if (!Directory.Exists(xdgDataHomeDefault)) + switch (dirpath) + { + case XDG_Type.CONFIG: + // Use 'XDG_CONFIG_HOME' if it's set, otherwise use the default path. + return string.IsNullOrEmpty(xdgconfighome) + ? xdgConfigHomeDefault + : Path.Combine(xdgconfighome, "powershell"); + + case XDG_Type.DATA: + // Use 'XDG_DATA_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgdatahome)) { - try - { - Directory.CreateDirectory(xdgDataHomeDefault); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + // Create the default data directory if it doesn't exist. + Directory.CreateDirectory(xdgDataHomeDefault); + return xdgDataHomeDefault; } - - return xdgDataHomeDefault; - } - else - { return Path.Combine(xdgdatahome, "powershell"); - } - case Platform.XDG_Type.USER_MODULES: - // the user has set XDG_DATA_HOME corresponding to module path - if (string.IsNullOrEmpty(xdgdatahome)) - { - // xdg values have not been set - if (!Directory.Exists(xdgModuleDefault)) // module folder not always guaranteed to exist + case XDG_Type.USER_MODULES: + // Use 'XDG_DATA_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgdatahome)) { - try - { - Directory.CreateDirectory(xdgModuleDefault); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + Directory.CreateDirectory(xdgModuleDefault); + return xdgModuleDefault; } - - return xdgModuleDefault; - } - else - { return Path.Combine(xdgdatahome, "powershell", "Modules"); - } - case Platform.XDG_Type.SHARED_MODULES: - return "/usr/local/share/powershell/Modules"; + case XDG_Type.SHARED_MODULES: + return "/usr/local/share/powershell/Modules"; - case Platform.XDG_Type.CACHE: - // the user has set XDG_CACHE_HOME - if (string.IsNullOrEmpty(xdgcachehome)) - { - // xdg values have not been set - if (!Directory.Exists(xdgCacheDefault)) // module folder not always guaranteed to exist - { - try - { - Directory.CreateDirectory(xdgCacheDefault); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } - } - - return xdgCacheDefault; - } - else - { - if (!Directory.Exists(Path.Combine(xdgcachehome, "powershell"))) + case XDG_Type.CACHE: + // Use 'XDG_CACHE_HOME' if it's set, otherwise use the default path. + if (string.IsNullOrEmpty(xdgcachehome)) { - try - { - Directory.CreateDirectory(Path.Combine(xdgcachehome, "powershell")); - } - catch (UnauthorizedAccessException) - { - // service accounts won't have permission to create user folder - return GetTemporaryDirectory(); - } + Directory.CreateDirectory(xdgCacheDefault); + return xdgCacheDefault; } - return Path.Combine(xdgcachehome, "powershell"); - } - - case Platform.XDG_Type.DEFAULT: - // default for profile location - return xdgConfigHomeDefault; + string cachePath = Path.Combine(xdgcachehome, "powershell"); + Directory.CreateDirectory(cachePath); + return cachePath; - default: - // xdgConfigHomeDefault needs to be created in the edge case that we do not have the folder or it was deleted - // This folder is the default in the event of all other failures for data storage - if (!Directory.Exists(xdgConfigHomeDefault)) - { - try - { - Directory.CreateDirectory(xdgConfigHomeDefault); - } - catch - { - Console.Error.WriteLine("Failed to create default data directory: " + xdgConfigHomeDefault); - } - } + case XDG_Type.DEFAULT: + // Use 'xdgConfigHomeDefault' for 'XDG_Type.DEFAULT' and create the directory if it doesn't exist. + Directory.CreateDirectory(xdgConfigHomeDefault); + return xdgConfigHomeDefault; - return xdgConfigHomeDefault; + default: + throw new InvalidOperationException("Unreachable code."); + } + } + catch (UnauthorizedAccessException) + { + // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts. + // Return an empty string in this case so the process working directory will be used. + return string.Empty; } } #endif /// - /// The code is copied from the .NET implementation. + /// 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 (string.Empty). - /// - private static string InternalGetFolderPath(System.Environment.SpecialFolder folder) - { - string folderPath = null; #if UNIX - string envHome = System.Environment.GetEnvironmentVariable(Platform.CommonEnvVariableNames.Home); - if (envHome == null) + return folder switch { - envHome = Platform.GetTemporaryDirectory(); - } - - switch (folder) - { - case System.Environment.SpecialFolder.ProgramFiles: - folderPath = "/bin"; - if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } - - break; - case System.Environment.SpecialFolder.ProgramFilesX86: - folderPath = "/usr/bin"; - if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } - - break; - case System.Environment.SpecialFolder.System: - case System.Environment.SpecialFolder.SystemX86: - folderPath = "/sbin"; - if (!System.IO.Directory.Exists(folderPath)) { folderPath = null; } - - break; - case System.Environment.SpecialFolder.Personal: - folderPath = envHome; - break; - case System.Environment.SpecialFolder.LocalApplicationData: - folderPath = System.IO.Path.Combine(envHome, ".config"); - if (!System.IO.Directory.Exists(folderPath)) - { - try - { - System.IO.Directory.CreateDirectory(folderPath); - } - catch (UnauthorizedAccessException) - { - // directory creation may fail if the account doesn't have filesystem permission such as some service accounts - folderPath = string.Empty; - } - } - - break; - default: - throw new NotSupportedException(); - } + Environment.SpecialFolder.ProgramFiles => Directory.Exists("/bin") ? "/bin" : string.Empty, + Environment.SpecialFolder.MyDocuments => GetHomeOrCreateTempHome(), + _ => throw new NotSupportedException() + }; #else - folderPath = System.Environment.GetFolderPath(folder); + return Environment.GetFolderPath(folder); #endif - return folderPath ?? string.Empty; } // Platform methods prefixed NonWindows are: @@ -558,11 +420,9 @@ internal static bool NonWindowsIsSameFileSystemItem(string pathOne, string pathT return Unix.NativeMethods.IsSameFileSystemItem(pathOne, pathTwo); } - internal static bool NonWindowsGetInodeData(string path, out System.ValueTuple inodeData) + internal static bool NonWindowsGetInodeData(string path, out ValueTuple inodeData) { - UInt64 device = 0UL; - UInt64 inode = 0UL; - var result = Unix.NativeMethods.GetInodeData(path, out device, out inode); + var result = Unix.NativeMethods.GetInodeData(path, out ulong device, out ulong inode); inodeData = (device, inode); return result == 0; @@ -1036,7 +896,7 @@ public static int GetProcFSParentPid(int pid) return invalidPid; } - return Int32.Parse(parts[3]); + return int.Parse(parts[3]); } catch (Exception) { @@ -1145,7 +1005,7 @@ internal static extern bool IsSameFileSystemItem([MarshalAs(UnmanagedType.LPStr) [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)] internal static extern int GetInodeData([MarshalAs(UnmanagedType.LPStr)] string path, - out UInt64 device, out UInt64 inode); + out ulong device, out ulong inode); /// /// This is a struct from getcommonstat.h in the native library. diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs index f518d18fa1c..d89c557b84d 100644 --- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs +++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs @@ -738,12 +738,12 @@ public static void Initialize(Collection errors, List moduleP if (!Directory.Exists(systemResourceRoot)) { - configSystemPath = Platform.GetFolderPath(Environment.SpecialFolder.System); + configSystemPath = Environment.GetFolderPath(Environment.SpecialFolder.System); systemResourceRoot = Path.Combine(configSystemPath, "Configuration"); inboxModulePath = InboxDscResourceModulePath; } - var programFilesDirectory = Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var programFilesDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); Debug.Assert(programFilesDirectory != null, "Program Files environment variable does not exist!"); var customResourceRoot = Path.Combine(programFilesDirectory, "WindowsPowerShell\\Configuration"); Debug.Assert(Directory.Exists(customResourceRoot), "%ProgramFiles%\\WindowsPowerShell\\Configuration Directory does not exist"); diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs index 3a641edc429..8f8d6c29d55 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -572,7 +572,7 @@ public FileInfoFormatting() /// /// Gets or sets the rendering mode for output. /// - public OutputRendering OutputRendering { get; set; } = OutputRendering.Host; + public OutputRendering OutputRendering { get; set; } = OutputRendering.Ansi; /// /// Gets value to turn off all attributes. diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index c7ba6db749d..1f298d296ab 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index a0411c78df7..9965824a927 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -1088,6 +1088,12 @@ public ValidateRangeAttribute(ValidateRangeKind kind) : base() private static void ValidateRange(object element, ValidateRangeKind rangeKind) { + if (element is TimeSpan ts) + { + ValidateTimeSpanRange(ts, rangeKind); + return; + } + Type commonType = GetCommonType(typeof(int), element.GetType()); if (commonType == null) { @@ -1212,6 +1218,59 @@ private void ValidateRange(object element) } } + private static void ValidateTimeSpanRange(TimeSpan element, ValidateRangeKind rangeKind) + { + TimeSpan zero = TimeSpan.Zero; + + switch (rangeKind) + { + case ValidateRangeKind.Positive: + if (zero.CompareTo(element) >= 0) + { + throw new ValidationMetadataException( + "ValidateRangePositiveFailure", + null, + Metadata.ValidateRangePositiveFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.NonNegative: + if (zero.CompareTo(element) > 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonNegativeFailure", + null, + Metadata.ValidateRangeNonNegativeFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.Negative: + if (zero.CompareTo(element) <= 0) + { + throw new ValidationMetadataException( + "ValidateRangeNegativeFailure", + null, + Metadata.ValidateRangeNegativeFailure, + element.ToString()); + } + + break; + case ValidateRangeKind.NonPositive: + if (zero.CompareTo(element) < 0) + { + throw new ValidationMetadataException( + "ValidateRangeNonPositiveFailure", + null, + Metadata.ValidateRangeNonPositiveFailure, + element.ToString()); + } + + break; + } + } + private static Type GetCommonType(Type minType, Type maxType) { Type resultType = null; diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 76be6418805..18bd6cbed3d 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -1813,9 +1813,14 @@ private List GetResultForIdentifier(CompletionContext completi } } } - - result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); - if (result != null) return result; + if (completionContext.TokenAtCursor.TokenFlags == TokenFlags.MemberName) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + if (result is not null) + { + return result; + } + } if ((tokenAtCursor.TokenFlags & TokenFlags.CommandName) != 0) { diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index 6d7fb77eba8..5de18cdc952 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -376,22 +376,16 @@ private static void VerifyRequiredSnapins(IEnumerable req foreach (var requiresPSSnapIn in requiresPSSnapIns) { - IEnumerable loadedPSSnapIns = null; - loadedPSSnapIns = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name); - if (loadedPSSnapIns == null || !loadedPSSnapIns.Any()) + var loadedPSSnapIn = context.InitialSessionState.GetPSSnapIn(requiresPSSnapIn.Name); + if (loadedPSSnapIn is null) { - if (requiresMissingPSSnapIns == null) - { - requiresMissingPSSnapIns = new Collection(); - } - + 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, + Dbg.Assert(loadedPSSnapIn.Version != null, string.Format( CultureInfo.InvariantCulture, "Version is null for loaded PSSnapin {0}.", loadedPSSnapIn)); @@ -400,11 +394,7 @@ private static void VerifyRequiredSnapins(IEnumerable req if (!AreInstalledRequiresVersionsCompatible( requiresPSSnapIn.Version, loadedPSSnapIn.Version)) { - if (requiresMissingPSSnapIns == null) - { - requiresMissingPSSnapIns = new Collection(); - } - + requiresMissingPSSnapIns ??= new Collection(); requiresMissingPSSnapIns.Add(BuildPSSnapInDisplayName(requiresPSSnapIn)); } } @@ -1063,14 +1053,12 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName, 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); @@ -1085,30 +1073,33 @@ private static CommandInfo TryModuleAutoDiscovery(string commandName, { // WinBlue:69141 - We need to get the full path here because the module path might be C:\Users\User1\DOCUME~1 // While the exportedCommands are cached, they are cached with the full path - string expandedModulePath = IO.Path.GetFullPath(modulePath); - string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(expandedModulePath); + string expandedModulePath = Path.GetFullPath(modulePath); + string moduleShortName = Path.GetFileNameWithoutExtension(expandedModulePath); var exportedCommands = AnalysisCache.GetExportedCommands(expandedModulePath, false, context); if (exportedCommands == null) { continue; } - CommandTypes exportedCommandTypes; // Skip if module only has class or other types and no commands. - if (exportedCommands.TryGetValue(commandName, out exportedCommandTypes)) + if (exportedCommands.TryGetValue(commandName, out CommandTypes exportedCommandTypes)) { - Exception exception; discoveryTracer.WriteLine("Found in module: {0}", expandedModulePath); - Collection matchingModule = AutoloadSpecifiedModule(expandedModulePath, context, + Collection matchingModule = AutoloadSpecifiedModule( + expandedModulePath, + context, cmdletInfo != null ? cmdletInfo.Visibility : SessionStateEntryVisibility.Private, - out exception); - lastError = exception; - if ((matchingModule == null) || (matchingModule.Count == 0)) + out lastError); + + if (matchingModule is null || matchingModule.Count == 0) { - string error = StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName); - CommandNotFoundException commandNotFound = new CommandNotFoundException( + string errorMessage = lastError is null + ? StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModule, commandName, moduleShortName) + : StringUtil.Format(DiscoveryExceptions.CouldNotAutoImportMatchingModuleWithErrorMessage, commandName, moduleShortName, lastError.Message); + + throw new CommandNotFoundException( originalCommandName, lastError, - "CouldNotAutoloadMatchingModule", error); - throw commandNotFound; + "CouldNotAutoloadMatchingModule", + errorMessage); } result = LookupCommandInfo(commandName, commandTypes, searchResolutionOptions, commandOrigin, context); diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index 5695d54806d..f6e3632e16e 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -25,6 +25,9 @@ public class ExperimentalFeature internal const string PSNativeCommandErrorActionPreferenceFeatureName = "PSNativeCommandErrorActionPreference"; internal const string PSRemotingSSHTransportErrorHandling = "PSRemotingSSHTransportErrorHandling"; internal const string PSCleanBlockFeatureName = "PSCleanBlock"; + internal const string PSAMSIMethodInvocationLogging = "PSAMSIMethodInvocationLogging"; + internal const string PSExecFeatureName = "PSExec"; + internal const string PSStrictModeAssignment = "PSStrictModeAssignment"; #endregion @@ -134,6 +137,15 @@ static ExperimentalFeature() new ExperimentalFeature( name: PSCleanBlockFeatureName, description: "Add support of a 'Clean' block to functions and script cmdlets for easy resource cleanup"), + new ExperimentalFeature( + name: PSAMSIMethodInvocationLogging, + description: "Provides AMSI notification of .NET method invocations."), + new ExperimentalFeature( + name: PSExecFeatureName, + description: "Add 'exec' built-in command on Linux and macOS"), + new ExperimentalFeature( + name: PSStrictModeAssignment, + description: "Add support of setting Strict-Mode with Invoke-Command"), }; EngineExperimentalFeatures = new ReadOnlyCollection(engineFeatures); diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 05eb871053d..bc40e903fa4 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -1663,11 +1663,6 @@ public InitialSessionState Clone() ss.DisableFormatUpdates = this.DisableFormatUpdates; - foreach (var s in this.defaultSnapins) - { - ss.defaultSnapins.Add(s); - } - foreach (var s in ImportedSnapins) { ss.ImportedSnapins.Add(s.Key, s.Value); @@ -3801,34 +3796,26 @@ public PSSnapInInfo ImportPSSnapIn(string name, out PSSnapInException warning) // Now actually load the snapin... PSSnapInInfo snapin = ImportPSSnapIn(newPSSnapIn, out warning); - if (snapin != null) - { - ImportedSnapins.Add(snapin.Name, snapin); - } return snapin; } internal PSSnapInInfo ImportCorePSSnapIn() { - // Load Microsoft.PowerShell.Core as a snapin + // Load Microsoft.PowerShell.Core as a snapin. PSSnapInInfo coreSnapin = PSSnapInReader.ReadCoreEngineSnapIn(); - this.defaultSnapins.Add(coreSnapin); - try - { - PSSnapInException warning; - this.ImportPSSnapIn(coreSnapin, out warning); - } - catch (PSSnapInException) - { - throw; - } - + ImportPSSnapIn(coreSnapin, out _); return coreSnapin; } internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInException warning) { + if (psSnapInInfo == null) + { + ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo)); + throw e; + } + // 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; @@ -3861,12 +3848,6 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce Dictionary> aliases = null; Dictionary providers = null; - if (psSnapInInfo == null) - { - ArgumentNullException e = new ArgumentNullException(nameof(psSnapInInfo)); - throw e; - } - Assembly assembly = null; string helpFile = null; @@ -3985,37 +3966,18 @@ internal PSSnapInInfo ImportPSSnapIn(PSSnapInInfo psSnapInInfo, out PSSnapInExce } } + ImportedSnapins.Add(psSnapInInfo.Name, psSnapInInfo); return psSnapInInfo; } - internal List GetPSSnapIn(string psSnapinName) + internal PSSnapInInfo GetPSSnapIn(string psSnapinName) { - List loadedSnapins = null; - foreach (var defaultSnapin in defaultSnapins) + if (ImportedSnapins.TryGetValue(psSnapinName, out PSSnapInInfo importedSnapin)) { - if (defaultSnapin.Name.Equals(psSnapinName, StringComparison.OrdinalIgnoreCase)) - { - if (loadedSnapins == null) - { - loadedSnapins = new List(); - } - - loadedSnapins.Add(defaultSnapin); - } - } - - PSSnapInInfo importedSnapin = null; - if (ImportedSnapins.TryGetValue(psSnapinName, out importedSnapin)) - { - if (loadedSnapins == null) - { - loadedSnapins = new List(); - } - - loadedSnapins.Add(importedSnapin); + return importedSnapin; } - return loadedSnapins; + return null; } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] @@ -4634,7 +4596,7 @@ internal static SessionStateAliasEntry[] BuiltInAliases const ScopedItemOptions ReadOnly_AllScope = ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope; const ScopedItemOptions ReadOnly = ScopedItemOptions.ReadOnly; - return new SessionStateAliasEntry[] { + var builtInAliases = new List { new SessionStateAliasEntry("foreach", "ForEach-Object", string.Empty, ReadOnly_AllScope), new SessionStateAliasEntry("%", "ForEach-Object", string.Empty, ReadOnly_AllScope), new SessionStateAliasEntry("where", "Where-Object", string.Empty, ReadOnly_AllScope), @@ -4801,6 +4763,15 @@ internal static SessionStateAliasEntry[] BuiltInAliases // - do not use AllScope - this causes errors in profiles that set this somewhat commonly used alias. new SessionStateAliasEntry("sls", "Select-String"), }; + +#if UNIX + if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSExecFeatureName)) + { + builtInAliases.Add(new SessionStateAliasEntry("exec", "Switch-Process")); + } +#endif + + return builtInAliases.ToArray(); } } @@ -4886,7 +4857,6 @@ internal static void RemoveAllDrivesForProvider(ProviderInfo pi, SessionStateInt internal static readonly string CoreSnapin = "Microsoft.PowerShell.Core"; internal static readonly string CoreModule = "Microsoft.PowerShell.Core"; - internal Collection defaultSnapins = new Collection(); // The list of engine modules to create warnings when you try to remove them internal static readonly HashSet EngineModules = new HashSet(StringComparer.OrdinalIgnoreCase) @@ -5439,6 +5409,13 @@ private static void InitializeCoreCmdletsAndProviders( cmdlets.Add("Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile)); } +#if UNIX + if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSExecFeatureName)) + { + cmdlets.Add("Switch-Process", new SessionStateCmdletEntry("Switch-Process", typeof(SwitchProcessCommand), helpFile)); + } +#endif + foreach (var val in cmdlets.Values) { val.SetPSSnapIn(psSnapInInfo); diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index d9d54539bac..c642e30e885 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -382,17 +382,6 @@ public void Dispose() private Exception _taskCollectionException; private string _currentLocationPath; - // List of Foreach-Object command names and aliases. - // TODO: Look into using SessionState.Internal.GetAliasTable() to find all user created aliases. - // But update Alias command logic to maintain reverse table that lists all aliases mapping - // to a single command definition, for performance. - private static readonly string[] forEachNames = new string[] - { - "ForEach-Object", - "foreach", - "%" - }; - private void InitParallelParameterSet() { // The following common parameters are not (yet) supported in this parameter set. @@ -423,8 +412,7 @@ private void InitParallelParameterSet() _usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesForEachParallel( scriptBlock: Parallel, isTrustedInput: allowUsingExpression, - context: this.Context, - foreachNames: forEachNames); + context: this.Context); // Validate using values map, which is a map of '$using:' variables referenced in the script. // Script block variables are not allowed since their behavior is undefined outside the runspace diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 2cbb30bcb00..45011edb908 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -636,6 +636,8 @@ private PSModuleInfo ImportModule_LocallyViaName_WithTelemetry(ImportModuleOptio private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModuleOptions, string name) { + bool shallWriteError = !importModuleOptions.SkipSystem32ModulesAndSuppressError; + try { bool found = false; @@ -671,14 +673,14 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } 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; - // TryGetFromModuleTable(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... if (!BaseForce && TryGetFromModuleTable(rootedPath, out PSModuleInfo module)) { @@ -725,9 +727,14 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul RemoveModule(moduleToRemove); } - foundModule = LoadModule(rootedPath, null, this.BasePrefix, null, ref importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out found); + foundModule = LoadModule( + fileName: rootedPath, + moduleBase: null, + prefix: BasePrefix, + ss: null, /*SessionState*/ + ref importModuleOptions, + manifestProcessingFlags, + out found); } else if (Directory.Exists(rootedPath)) { @@ -738,21 +745,24 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } // Load the latest valid version if it is a multi-version module directory - foundModule = LoadUsingMultiVersionModuleBase(rootedPath, - ManifestProcessingFlags.LoadElements | - ManifestProcessingFlags.WriteErrors | - ManifestProcessingFlags.NullOnFirstError, - importModuleOptions, out found); + foundModule = LoadUsingMultiVersionModuleBase(rootedPath, manifestProcessingFlags, importModuleOptions, out found); if (!found) { // If the path is a directory, double up the end of the string // then try to load that using extensions... rootedPath = Path.Combine(rootedPath, Path.GetFileName(rootedPath)); - foundModule = LoadUsingExtensions(null, rootedPath, rootedPath, null, null, this.BasePrefix, /*SessionState*/ null, - importModuleOptions, - ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - out found); + foundModule = LoadUsingExtensions( + parentModule: null, + moduleName: rootedPath, + fileBaseName: rootedPath, + extension: null, + moduleBase: null, + prefix: BasePrefix, + ss: null, /*SessionState*/ + importModuleOptions, + manifestProcessingFlags, + out found); } } } @@ -762,7 +772,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) @@ -786,16 +796,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 @@ -807,14 +829,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; @@ -856,8 +881,10 @@ private PSModuleInfo ImportModule_LocallyViaName(ImportModuleOptions importModul } catch (PSInvalidOperationException e) { - ErrorRecord er = new ErrorRecord(e.ErrorRecord, e); - WriteError(er); + if (shallWriteError) + { + WriteError(new ErrorRecord(e.ErrorRecord, e)); + } } return null; @@ -1945,27 +1972,26 @@ private bool IsModuleInDenyList(string[] moduleDenyList, string moduleName, Modu return match; } - private List FilterModuleCollection(IEnumerable moduleCollection) + private IEnumerable FilterModuleCollection(IEnumerable moduleCollection) { - List filteredModuleCollection = null; - if (moduleCollection != null) + if (moduleCollection is null) { - // the ModuleDeny list is cached in PowerShellConfig object - string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList(); - if (moduleDenyList?.Any() != true) - { - filteredModuleCollection = new List(moduleCollection); - } - else + return null; + } + + // The ModuleDeny list is cached in PowerShellConfig object + string[] moduleDenyList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityModuleDenyList(); + if (moduleDenyList is null || moduleDenyList.Length == 0) + { + return moduleCollection; + } + + var filteredModuleCollection = new List(); + foreach (var module in moduleCollection) + { + if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification)) { - filteredModuleCollection = new List(); - foreach (var module in moduleCollection) - { - if (!IsModuleInDenyList(moduleDenyList, module as string, module as ModuleSpecification)) - { - filteredModuleCollection.Add(module); - } - } + filteredModuleCollection.Add(module); } } @@ -1977,38 +2003,73 @@ private void PrepareNoClobberWinCompatModuleImport(string moduleName, ModuleSpec Debug.Assert(string.IsNullOrEmpty(moduleName) ^ (moduleSpec == null), "Either moduleName or moduleSpec must be specified"); // moduleName can be just a module name and it also can be a full path to psd1 from which we need to extract the module name - string coreModuleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec == null ? moduleName : moduleSpec.Name); + string moduleToLoad = ModuleIntrinsics.GetModuleName(moduleSpec is null ? moduleName : moduleSpec.Name); + + var isBuiltInModule = BuiltInModules.TryGetValue(moduleToLoad, out string normalizedName); + if (isBuiltInModule) + { + moduleToLoad = normalizedName; + } - var isModuleToLoadEngineModule = InitialSessionState.IsEngineModule(coreModuleToLoad); string[] noClobberModuleList = PowerShellConfig.Instance.GetWindowsPowerShellCompatibilityNoClobberModuleList(); - if (isModuleToLoadEngineModule || ((noClobberModuleList != null) && noClobberModuleList.Contains(coreModuleToLoad, StringComparer.OrdinalIgnoreCase))) + if (isBuiltInModule || noClobberModuleList?.Contains(moduleToLoad, StringComparer.OrdinalIgnoreCase) == true) { - // if it is one of engine modules - first try to load it from $PSHOME\Modules - // otherwise rely on $env:PSModulePath (in which WinPS module location has to go after CorePS module location) - if (isModuleToLoadEngineModule) + bool shouldLoadModuleLocally = true; + if (isBuiltInModule) { - string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), coreModuleToLoad); - if (Directory.Exists(expectedCoreModulePath)) + PSSnapInInfo loadedSnapin = Context.CurrentRunspace.InitialSessionState.GetPSSnapIn(moduleToLoad); + shouldLoadModuleLocally = loadedSnapin is null; + + if (shouldLoadModuleLocally) { - coreModuleToLoad = expectedCoreModulePath; + // If it is one of built-in modules, first try loading it from $PSHOME\Modules, otherwise rely on $env:PSModulePath. + string expectedCoreModulePath = Path.Combine(ModuleIntrinsics.GetPSHomeModulePath(), moduleToLoad); + if (Directory.Exists(expectedCoreModulePath)) + { + moduleToLoad = expectedCoreModulePath; + } } } - if (moduleSpec == null) - { - ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, coreModuleToLoad); - } - else + if (shouldLoadModuleLocally) { - ModuleSpecification tmpModuleSpec = new ModuleSpecification() + // Here we want to load a core-edition compatible version of the module, so the loading procedure will skip + // the 'System32' module path when searching. Also, we want to suppress writing out errors in case that a + // core-compatible version of the module cannot be found, because: + // 1. that's OK as long as it's not a PowerShell built-in module such as the 'Utility' moudle; + // 2. the error message will be confusing to the user. + bool savedValue = importModuleOptions.SkipSystem32ModulesAndSuppressError; + importModuleOptions.SkipSystem32ModulesAndSuppressError = true; + + PSModuleInfo moduleInfo = moduleSpec is null + ? ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, moduleToLoad) + : ImportModule_LocallyViaFQName( + importModuleOptions, + new ModuleSpecification() + { + Guid = moduleSpec.Guid, + MaximumVersion = moduleSpec.MaximumVersion, + Version = moduleSpec.Version, + RequiredVersion = moduleSpec.RequiredVersion, + Name = moduleToLoad + }); + + // If we failed to load a core-compatible version of a built-in module, we should stop trying to load the + // module in 'WinCompat' mode and report an error. This could happen when a user didn't correctly deploy + // the built-in modules, which would result in very confusing errors when the module auto-loading silently + // attempts to load those built-in modules in 'WinCompat' mode from the 'System32' module path. + // + // If the loading failed but it's NOT a built-in module, then it's fine to ignore this failure and continue + // to load the module in 'WinCompat' mode. + if (moduleInfo is null && isBuiltInModule) { - Guid = moduleSpec.Guid, - MaximumVersion = moduleSpec.MaximumVersion, - Version = moduleSpec.Version, - RequiredVersion = moduleSpec.RequiredVersion, - Name = coreModuleToLoad - }; - ImportModule_LocallyViaFQName(importModuleOptions, tmpModuleSpec); + throw new InvalidOperationException( + StringUtil.Format( + Modules.CannotFindCoreCompatibleBuiltInModule, + moduleToLoad)); + } + + importModuleOptions.SkipSystem32ModulesAndSuppressError = savedValue; } importModuleOptions.NoClobberExportPSSession = true; @@ -2020,28 +2081,15 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable moduleProxyList = new List(); #if !UNIX // one of the two parameters can be passed: either ModuleNames (most of the time) or ModuleSpecifications (they are used in different parameter sets) - List filteredModuleNames = FilterModuleCollection(moduleNames); - List filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames); + IEnumerable filteredModuleNames = FilterModuleCollection(moduleNames); + IEnumerable filteredModuleFullyQualifiedNames = FilterModuleCollection(moduleFullyQualifiedNames); // do not setup WinCompat resources if we have no modules to import - if ((filteredModuleNames?.Any() != true) && (filteredModuleFullyQualifiedNames?.Any() != true)) + if (filteredModuleNames?.Any() != true && filteredModuleFullyQualifiedNames?.Any() != true) { return moduleProxyList; } - var winPSVersionString = Utils.GetWindowsPowerShellVersionFromRegistry(); - if (!winPSVersionString.StartsWith("5.1", StringComparison.OrdinalIgnoreCase)) - { - string errorMessage = string.Format(CultureInfo.InvariantCulture, Modules.WinCompatRequredVersionError, winPSVersionString); - throw new InvalidOperationException(errorMessage); - } - - PSSession WindowsPowerShellCompatRemotingSession = CreateWindowsPowerShellCompatResources(); - if (WindowsPowerShellCompatRemotingSession == null) - { - return new List(); - } - // perform necessary preparations if module has to be imported with NoClobber mode if (filteredModuleNames != null) { @@ -2059,13 +2107,26 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable(); + } + // perform the module import / proxy generation moduleProxyList = ImportModule_RemotelyViaPsrpSession(importModuleOptions, filteredModuleNames, filteredModuleFullyQualifiedNames, WindowsPowerShellCompatRemotingSession, usingWinCompat: true); foreach (PSModuleInfo moduleProxy in moduleProxyList) { moduleProxy.IsWindowsPowerShellCompatModule = true; - System.Threading.Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter); + Interlocked.Increment(ref s_WindowsPowerShellCompatUsageCounter); string message = StringUtil.Format(Modules.WinCompatModuleWarning, moduleProxy.Name, WindowsPowerShellCompatRemotingSession.Name); WriteWarning(message); diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index 90019e3bfed..a4d1a7057f1 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -105,6 +105,12 @@ protected internal struct ImportModuleOptions /// Historically -AllowClobber in these scenarios was set as True. /// internal bool NoClobberExportPSSession; + + /// + /// Flag that controls skipping the System32 module path when searching a module in module paths. It also suppresses + /// writing out errors when specified. + /// + internal bool SkipSystem32ModulesAndSuppressError; } /// @@ -280,6 +286,21 @@ internal List MatchAll "ModuleVersion" }; + /// + /// List of PowerShell built-in modules that are shipped with PowerShell only, not on PS Gallery. + /// + protected static readonly HashSet BuiltInModules = new(StringComparer.OrdinalIgnoreCase) + { + "CimCmdlets", + "Microsoft.PowerShell.Diagnostics", + "Microsoft.PowerShell.Host", + "Microsoft.PowerShell.Management", + "Microsoft.PowerShell.Security", + "Microsoft.PowerShell.Utility", + "Microsoft.WSMan.Management", + "PSDiagnostics", + }; + /// /// When module manifests lack a CompatiblePSEditions field, /// they will be treated as if they have this value. @@ -307,14 +328,25 @@ internal List MatchAll private readonly Dictionary _currentlyProcessingModules = new Dictionary(); - internal bool LoadUsingModulePath(bool found, IEnumerable modulePath, string name, SessionState ss, - ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module) + internal bool LoadUsingModulePath( + IEnumerable modulePath, + string name, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + out PSModuleInfo module) { - return LoadUsingModulePath(null, found, modulePath, name, ss, options, manifestProcessingFlags, out module); + return LoadUsingModulePath(parentModule: null, modulePath, name, ss, options, manifestProcessingFlags, out module); } - internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumerable modulePath, string name, SessionState ss, - ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out PSModuleInfo module) + internal bool LoadUsingModulePath( + PSModuleInfo parentModule, + IEnumerable modulePath, + string name, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + out PSModuleInfo module) { string extension = Path.GetExtension(name); string fileBaseName; @@ -325,11 +357,18 @@ internal bool LoadUsingModulePath(PSModuleInfo parentModule, bool found, IEnumer extension = null; } else + { fileBaseName = name.Substring(0, name.Length - extension.Length); + } // Now search using the module path... + bool found = false; foreach (string path in modulePath) { + if (options.SkipSystem32ModulesAndSuppressError && ModuleUtils.IsOnSystem32ModulePath(path)) + { + continue; + } #if UNIX foreach (string folder in Directory.EnumerateDirectories(path)) { @@ -823,7 +862,7 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module } // Otherwise try the module path - found = LoadUsingModulePath(parentModule, found, modulePath, + found = LoadUsingModulePath(parentModule, modulePath, moduleSpecification.Name, ss, options, manifestProcessingFlags, out module); } @@ -2375,17 +2414,13 @@ internal PSModuleInfo LoadModuleManifest( { if (importingModule) { - IList moduleProxies = ImportModulesUsingWinCompat(new string[] { moduleManifestPath }, null, options); + IList moduleProxies = ImportModulesUsingWinCompat( + moduleNames: new string[] { moduleManifestPath }, + moduleFullyQualifiedNames: null, + importModuleOptions: options); - // we are loading by a single ManifestPath so expect max of 1 - if (moduleProxies.Count > 0) - { - return moduleProxies[0]; - } - else - { - return null; - } + // We are loading by a single ManifestPath so expect max of 1 + return moduleProxies.Count > 0 ? moduleProxies[0] : null; } } else @@ -3706,7 +3741,7 @@ internal static object IsModuleLoaded(ExecutionContext context, ModuleSpecificat // If the RequiredModule is one of the Engine modules, then they could have been loaded as snapins (using InitialSessionState.CreateDefault()) if (result == null && InitialSessionState.IsEngineModule(requiredModule.Name)) { - result = ModuleCmdletBase.GetEngineSnapIn(context, requiredModule.Name); + result = context.CurrentRunspace.InitialSessionState.GetPSSnapIn(requiredModule.Name); if (result != null) { loaded = true; @@ -4883,7 +4918,7 @@ internal static void SyncCurrentLocationHandler(object sender, LocationChangedEv } } - internal static System.EventHandler SyncCurrentLocationDelegate; + internal static EventHandler SyncCurrentLocationDelegate; internal virtual IList ImportModulesUsingWinCompat(IEnumerable moduleNames, IEnumerable moduleFullyQualifiedNames, ImportModuleOptions importModuleOptions) { throw new System.NotImplementedException(); } @@ -5309,9 +5344,8 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, string moduleName, string fileBaseName, string extension, string moduleBase, string prefix, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, out bool found) { - bool throwAwayModuleFileFound = false; return LoadUsingExtensions(parentModule, moduleName, fileBaseName, extension, moduleBase, prefix, ss, - options, manifestProcessingFlags, out found, out throwAwayModuleFileFound); + options, manifestProcessingFlags, out found, out _); } /// @@ -5412,7 +5446,6 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, } else if (File.Exists(fileName)) { - moduleFileFound = true; // Win8: 325243 - Added the version check so that we do not unload modules with the same name but different version if (BaseForce && DoesAlreadyLoadedModuleSatisfyConstraints(module)) { @@ -7359,29 +7392,6 @@ private static void ValidateCommandName(ModuleCmdletBase cmdlet, } } - /// - /// Search a PSSnapin with the specified name. - /// - internal static PSSnapInInfo GetEngineSnapIn(ExecutionContext context, string name) - { - HashSet snapinSet = new HashSet(); - List cmdlets = context.SessionState.InvokeCommand.GetCmdlets(); - foreach (CmdletInfo cmdlet in cmdlets) - { - PSSnapInInfo snapin = cmdlet.PSSnapIn; - if (snapin != null && !snapinSet.Contains(snapin)) - snapinSet.Add(snapin); - } - - foreach (PSSnapInInfo snapin in snapinSet) - { - if (string.Equals(snapin.Name, name, StringComparison.OrdinalIgnoreCase)) - return snapin; - } - - return null; - } - /// /// Returns the context cached ModuleTable module for import only if found and has safe language boundaries while /// exporting all functions by default. diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 1d8fe6bc8b5..ada03fb4914 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -976,20 +976,17 @@ internal static string GetPSHomeModulePath() try { string psHome = Utils.DefaultPowerShellAppBase; - if (!string.IsNullOrEmpty(psHome)) - { - // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed - // This happens because ModuleTable uses Path as the key and CBS installer - // expands the path to include "SysWOW64" (for - // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\PowerShell\3\PowerShellEngine ApplicationBase). - // Because of this, the module that is getting loaded during startup (through LocalRunspace) - // is using "SysWow64" in the key. Later, when Import-Module is called, it loads the - // module using ""System32" in the key. #if !UNIX - psHome = psHome.ToLowerInvariant().Replace("\\syswow64\\", "\\system32\\"); + // Win8: 584267 Powershell Modules are listed twice in x86, and cannot be removed. + // This happens because 'ModuleTable' uses Path as the key and x86 WinPS has "SysWOW64" in its $PSHOME. + // Because of this, the module that is getting loaded during startup (through LocalRunspace) is using + // "SysWow64" in the key. Later, when 'Import-Module' is called, it loads the module using ""System32" + // in the key. + // For the cross-platform PowerShell, a user can choose to install it under "C:\Windows\SysWOW64", and + // thus it may have the same problem as described above. So we keep this line of code. + psHome = psHome.ToLowerInvariant().Replace(@"\syswow64\", @"\system32\"); #endif - Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null); - } + Interlocked.CompareExchange(ref s_psHomeModulePath, Path.Combine(psHome, "Modules"), null); } catch (System.Security.SecurityException) { diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index 26d3174ca3b..8dab2283fc5 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs @@ -121,7 +121,7 @@ internal static bool IsPSEditionCompatible( #if UNIX return true; #else - if (!ModuleUtils.IsOnSystem32ModulePath(moduleManifestPath)) + if (!IsOnSystem32ModulePath(moduleManifestPath)) { return true; } 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..298587679b1 --- /dev/null +++ b/src/System.Management.Automation/engine/Modules/SwitchProcessCommand.cs @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +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. + /// + [Experimental(ExperimentalFeature.PSExecFeatureName, ExperimentAction.Show)] + [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, + command + ) + ), + "CommandNotFound", + ErrorCategory.InvalidArgument, + WithCommand + ) + ); + } + + 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; + + int exitCode = Exec(command.Source, execArgs); + + if (exitCode < 0) + { + 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. + /// + /// + /// 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); + } +} + +#endif diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index 59715a02125..200cfeee2ef 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -314,7 +314,7 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI } else { - if (argArrayAst != null && ArgumentPassingStyle == NativeArgumentPassingStyle.Standard) + 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)) @@ -331,7 +331,7 @@ private void AppendOneNativeArgument(ExecutionContext context, CommandParameterI } } } - else if (ArgumentPassingStyle == NativeArgumentPassingStyle.Standard && currentObj != null) + else if (ArgumentPassingStyle != NativeArgumentPassingStyle.Legacy && currentObj != null) { // add empty strings to arglist, but not nulls AddToArgumentList(parameter, arg); diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 12c02e60783..efc8b660c37 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -545,15 +545,14 @@ private void InitNativeProcess() Exception exceptionToRethrow = null; try { - // If this process is being run standalone, tell the host, which might want - // to save off the window title or other such state as might be tweaked by - // the native process + // Before start the executable, tell the host, which might want to save off the + // window title or other such state as might be tweaked by the native process. + Command.Context.EngineHostInterface.NotifyBeginApplication(); + _hasNotifiedBeginApplication = true; + if (_runStandAlone) { - this.Command.Context.EngineHostInterface.NotifyBeginApplication(); - _hasNotifiedBeginApplication = true; - - // Also, store the Raw UI coordinates so that we can scrape the screen after + // Store the Raw UI coordinates so that we can scrape the screen after // if we are transcribing. if (_isTranscribing && (s_supportScreenScrape == true)) { @@ -1166,7 +1165,7 @@ private void CleanUp() // We need to call 'NotifyEndApplication' as appropriate during cleanup if (_hasNotifiedBeginApplication) { - this.Command.Context.EngineHostInterface.NotifyEndApplication(); + Command.Context.EngineHostInterface.NotifyEndApplication(); } try diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index 6c724a2132b..99740a44d13 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -729,7 +729,7 @@ private static bool TryParseVersion(string version, ref VersionResult result) } else { - if (dashIndex == -1) + if (plusIndex == -1) { // Here dashIndex == plusIndex == -1 // No preLabel - preLabel == null; @@ -737,6 +737,13 @@ private static bool TryParseVersion(string version, ref VersionResult result) // Format is 'major.minor.patch' versionSansLabel = version; } + else if (dashIndex == -1) + { + // No PreReleaseLabel: preLabel == null + // Format is 'major.minor.patch+BuildLabel' + buildLabel = version.Substring(plusIndex + 1); + versionSansLabel = version.Substring(0, plusIndex); + } else { // Format is 'major.minor.patch-PreReleaseLabel+BuildLabel' diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index f277dcb1e49..879f43944a5 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -1574,7 +1574,6 @@ public static class InternalTestHooks internal static bool BypassAppLockerPolicyCaching; internal static bool BypassOnlineHelpRetrieval; internal static bool ForcePromptForChoiceDefaultOption; - internal static bool BypassOutputRedirectionCheck; internal static bool NoPromptForPassword; internal static bool ForceFormatListFixedLabelWidth; diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 7f10b75525c..9f195d7e687 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -363,7 +363,7 @@ public override Debugger Debugger } } - private static readonly string s_debugPreferenceCachePath = Path.Combine(Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell"), "DebugPreference.clixml"); + private static readonly string s_debugPreferenceCachePath = Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell", "DebugPreference.clixml"); private static readonly object s_debugPreferenceLockObject = new object(); /// @@ -1242,8 +1242,6 @@ protected override void Dispose(bool disposing) RunspaceOpening = null; } - Platform.RemoveTemporaryDirectory(); - // Dispose the event manager if (this.ExecutionContext != null && this.ExecutionContext.Events != null) { diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index ea101ade659..636e888d475 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -304,18 +304,12 @@ public enum FormatStyle /// /// The format style to get the escape sequence for. /// - /// - /// True if the output is redirected. - /// /// /// The ANSI escape sequence for the given format style. /// - public static string GetFormatStyleString(FormatStyle formatStyle, bool isOutputRedirected) + public static string GetFormatStyleString(FormatStyle formatStyle) { - // redirected console gets plaintext output to preserve existing behavior - if (!InternalTestHooks.BypassOutputRedirectionCheck && - (PSStyle.Instance.OutputRendering == OutputRendering.PlainText || - isOutputRedirected)) + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) { return string.Empty; } @@ -353,30 +347,22 @@ public static string GetFormatStyleString(FormatStyle formatStyle, bool isOutput /// /// True if the host supports virtual terminal. /// - /// - /// True if the output is redirected. - /// /// /// The formatted text. /// - public static string GetOutputString(string text, bool supportsVirtualTerminal, bool isOutputRedirected) + public static string GetOutputString(string text, bool supportsVirtualTerminal) { - return GetOutputString(text, isHost: true, supportsVirtualTerminal: supportsVirtualTerminal, isOutputRedirected: isOutputRedirected); + return GetOutputString(text, isHost: true, supportsVirtualTerminal: supportsVirtualTerminal); } - internal static string GetOutputString(string text, bool isHost, bool? supportsVirtualTerminal = null, bool isOutputRedirected = false) + internal static string GetOutputString(string text, bool isHost, bool? supportsVirtualTerminal = null) { var sd = new ValueStringDecorated(text); if (sd.IsDecorated) { var outputRendering = OutputRendering.Ansi; - if (InternalTestHooks.BypassOutputRedirectionCheck) - { - isOutputRedirected = false; - } - - if (isOutputRedirected || ShouldOutputPlainText(isHost, supportsVirtualTerminal)) + if (ShouldOutputPlainText(isHost, supportsVirtualTerminal)) { outputRendering = OutputRendering.PlainText; } diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 9956fb29e30..b8ca57b4b05 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -639,7 +639,9 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo ArgumentTransformationAttribute_Transform = typeof(ArgumentTransformationAttribute).GetMethod(nameof(ArgumentTransformationAttribute.Transform), InstancePublicFlags); - // ReSharper restore InconsistentNaming + + internal static readonly MethodInfo MemberInvocationLoggingOps_LogMemberInvocation = + typeof(MemberInvocationLoggingOps).GetMethod(nameof(MemberInvocationLoggingOps.LogMemberInvocation), StaticFlags); } internal static class ExpressionCache diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 8fbb262edad..e3d08ca38ab 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -6735,7 +6735,7 @@ private ExpressionAst ExpressionRule(bool endNumberOnTernaryOpChars = false) SkipToken(); SkipNewlines(); - // We have seen the ternary operator '?' and now expecting the 'IfFalse' expression. + // We have seen the ternary operator '?' and now expecting the 'IfTrue' expression. ExpressionAst ifTrue = ExpressionRule(endNumberOnTernaryOpChars: true); if (ifTrue == null) { diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index 6c7efa68f28..26778bfea5f 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -3226,7 +3226,7 @@ protected virtual void HandleOperationComplete(object sender, OperationStateEven // no pipeline is created and no pipeline state changed event is raised. // We can wait for throttle complete, but it is raised only when all the // operations are completed and this means that status of job is not updated - // untill Operation Complete. + // until Operation Complete. ExecutionCmdletHelper helper = sender as ExecutionCmdletHelper; Dbg.Assert(helper != null, "Sender of OperationComplete has to be ExecutionCmdletHelper"); diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs index 8b8908b6e61..07446d45fc3 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs @@ -813,7 +813,7 @@ private void HandleReadyForDisconnect(object sender, EventArgs args) // what thread this callback is made from. If it was made from a transport // callback event then a deadlock may occur when DisconnectAsync is called on // that same thread. - ThreadPool.QueueUserWorkItem(new WaitCallback(StartDisconnectAsync), RemoteSession); + ThreadPool.QueueUserWorkItem(new WaitCallback(StartDisconnectAsync)); } } } @@ -821,10 +821,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. + } } /// diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index 7ddd657872e..f2e9bd9093e 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -221,11 +221,7 @@ public override InitialSessionState InitialSessionState { get { -#pragma warning disable 56503 - throw PSTraceSource.NewNotImplementedException(); - -#pragma warning restore 56503 } } @@ -236,11 +232,7 @@ public override JobManager JobManager { get { -#pragma warning disable 56503 - throw PSTraceSource.NewNotImplementedException(); - -#pragma warning restore 56503 } } diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index 19c093e3687..76601ff6dfb 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -2320,13 +2320,8 @@ public Version PSVersion set { - RemotingCommandUtil.CheckPSVersion(value); - - // Check if specified version of PowerShell is installed - RemotingCommandUtil.CheckIfPowerShellVersionIsInstalled(value); - - psVersion = value; - isPSVersionSpecified = true; + // PowerShell 7 remoting endpoints do not support PSVersion. + throw new PSNotSupportedException(RemotingErrorIdStrings.PowerShellVersionNotSupported); } } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index c86f7903a1a..a3aca6de70c 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -263,6 +263,72 @@ public override SwitchParameter UseSSL } } + private sealed class ArgumentToPSVersionTransformationAttribute : ArgumentToVersionTransformationAttribute + { + protected override bool TryConvertFromString(string versionString, [NotNullWhen(true)] out Version version) + { + if (string.Equals("off", versionString, StringComparison.OrdinalIgnoreCase)) + { + version = new Version(0, 0); + return true; + } + + if (string.Equals("latest", versionString, StringComparison.OrdinalIgnoreCase)) + { + version = PSVersionInfo.PSVersion; + return true; + } + + return base.TryConvertFromString(versionString, out version); + } + } + + private static readonly Version s_OffVersion = new Version(0, 0); + + private sealed class ValidateVersionAttribute : ValidateArgumentsAttribute + { + protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) + { + Version version = arguments as Version; + if (version == s_OffVersion) + { + return; + } + + if (version == null || !PSVersionInfo.IsValidPSVersion(version)) + { + // No conversion succeeded so throw an exception... + throw new ValidationMetadataException( + "InvalidPSVersion", + null, + Metadata.ValidateVersionFailure, + arguments); + } + } + } + + /// + /// Gets or sets strict mode. + /// + [Experimental(ExperimentalFeature.PSStrictModeAssignment, ExperimentAction.Show)] + [Parameter(ParameterSetName = InvokeCommandCommand.InProcParameterSet)] + [ArgumentToPSVersionTransformation] + [ValidateVersion] + public Version StrictMode + { + get + { + return _strictmodeversion; + } + + set + { + _strictmodeversion = value; + } + } + + private Version _strictmodeversion = null; + /// /// For WSMan session: /// If this parameter is not specified then the value specified in @@ -823,6 +889,8 @@ public virtual SwitchParameter RemoteDebug #endregion + private Version _savedStrictModeVersion; + #endregion Parameters #region Overrides @@ -946,6 +1014,12 @@ protected override void BeginProcessing() } } + if (_strictmodeversion != null) + { + _savedStrictModeVersion = Context.EngineSessionState.CurrentScope.StrictModeVersion; + Context.EngineSessionState.CurrentScope.StrictModeVersion = _strictmodeversion; + } + return; } @@ -1162,7 +1236,19 @@ protected override void ProcessRecord() } else if (ParameterSetName.Equals(InvokeCommandCommand.InProcParameterSet) && (_steppablePipeline != null)) { - _steppablePipeline.Process(InputObject); + try + { + _steppablePipeline.Process(InputObject); + } + catch + { + if (_strictmodeversion != null) + { + Context.EngineSessionState.CurrentScope.StrictModeVersion = _savedStrictModeVersion; + } + + throw; + } } else { @@ -1193,20 +1279,30 @@ protected override void EndProcessing() { if (ParameterSetName.Equals(InvokeCommandCommand.InProcParameterSet)) { - if (_steppablePipeline != null) - { - _steppablePipeline.End(); + try + { + if (_steppablePipeline != null) + { + _steppablePipeline.End(); + } + else + { + ScriptBlock.InvokeUsingCmdlet( + contextCmdlet: this, + useLocalScope: !NoNewScope, + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: AutomationNull.Value, + input: _input, + scriptThis: AutomationNull.Value, + args: ArgumentList); + } } - else + finally { - ScriptBlock.InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: !NoNewScope, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: AutomationNull.Value, - input: _input, - scriptThis: AutomationNull.Value, - args: ArgumentList); + if (_strictmodeversion != null) + { + Context.EngineSessionState.CurrentScope.StrictModeVersion = _savedStrictModeVersion; + } } } else diff --git a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs index 90e10bb8f44..ff7b844d7fa 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs @@ -512,10 +512,12 @@ public virtual Version PSVersion set { - RemotingCommandUtil.CheckPSVersion(value); - - // Check if specified version of PowerShell is installed - RemotingCommandUtil.CheckIfPowerShellVersionIsInstalled(value); + // PSVersion value can only be 5.1 for Start-Job. + if (!(value.Major == 5 && value.Minor == 1)) + { + throw new ArgumentException( + StringUtil.Format(RemotingErrorIdStrings.PSVersionParameterOutOfRange, value, "PSVersion")); + } _psVersion = value; } diff --git a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs index 094dcedc6ad..f30e4ba5109 100644 --- a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs +++ b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs @@ -165,60 +165,5 @@ internal static void CheckHostRemotingPrerequisites() throw new InvalidOperationException(errorRecord.ToString()); } } - - internal static void CheckPSVersion(Version version) - { - // PSVersion value can only be 2.0, 3.0, 4.0, 5.0, or 5.1 - if (version != null) - { - // PSVersion value can only be 2.0, 3.0, 4.0, 5.0, or 5.1 - if (!(version.Major >= 2 && version.Major <= 4 && version.Minor == 0) && - !(version.Major == 5 && version.Minor <= 1)) - { - throw new ArgumentException( - StringUtil.Format(RemotingErrorIdStrings.PSVersionParameterOutOfRange, version, "PSVersion")); - } - } - } - - /// - /// Checks if the specified version of PowerShell is installed. - /// - /// - internal static void CheckIfPowerShellVersionIsInstalled(Version version) - { - // Check if PowerShell 2.0 is installed - if (version != null && version.Major == 2) - { -#if CORECLR - // PowerShell 2.0 is not available for CoreCLR - throw new ArgumentException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.PowerShellNotInstalled, - version, "PSVersion")); -#else - // Because of app-compat issues, in Win8, we will have PS 2.0 installed by default but not .NET 2.0 - // In such a case, it is not enough if we check just PowerShell registry keys. We also need to check if .NET 2.0 is installed. - try - { - RegistryKey engineKey = PSSnapInReader.GetPSEngineKey(PSVersionInfo.RegistryVersion1Key); - // Also check for .NET 2.0 installation - if (!PsUtils.FrameworkRegistryInstallation.IsFrameworkInstalled(2, 0, 0)) - { - throw new ArgumentException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.NetFrameWorkV2NotInstalled)); - } - } - catch (PSArgumentException) - { - throw new ArgumentException( - PSRemotingErrorInvariants.FormatResourceString( - RemotingErrorIdStrings.PowerShellNotInstalled, - version, "PSVersion")); - } -#endif - } - } } } diff --git a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs index 7baebdc98ae..a29a202b5ae 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs @@ -220,21 +220,29 @@ internal byte[] ReadOrRegisterCallback(OnDataAvailableCallback callback, lock (_readSyncObject) { priorityType = DataPriorityType.Default; - - // send data from which ever stream that has data directly. + // Send data from which ever stream that has data directly. byte[] result = null; - result = _dataToBeSent[(int)DataPriorityType.PromptResponse].ReadOrRegisterCallback(_onSendCollectionDataAvailable); - priorityType = DataPriorityType.PromptResponse; + SerializedDataStream promptDataToBeSent = _dataToBeSent[(int)DataPriorityType.PromptResponse]; + if (promptDataToBeSent is not null) + { + result = promptDataToBeSent.ReadOrRegisterCallback(_onSendCollectionDataAvailable); + priorityType = DataPriorityType.PromptResponse; + } if (result == null) { - result = _dataToBeSent[(int)DataPriorityType.Default].ReadOrRegisterCallback(_onSendCollectionDataAvailable); - priorityType = DataPriorityType.Default; + SerializedDataStream defaultDataToBeSent = _dataToBeSent[(int)DataPriorityType.Default]; + if (defaultDataToBeSent is not null) + { + result = defaultDataToBeSent.ReadOrRegisterCallback(_onSendCollectionDataAvailable); + priorityType = DataPriorityType.Default; + } } - // no data to return..so register the callback. + + // No data to return..so register the callback. if (result == null) { - // register callback. + // Register callback. _onDataAvailableCallback = callback; } diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index a4917d5af6b..52f85e372b2 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -6903,6 +6903,23 @@ internal static DynamicMetaObject InvokeDotNetMethod( expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant); } + if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSAMSIMethodInvocationLogging)) + { + // Expression block runs two expressions in order: + // - Log method invocation to AMSI Notifications (can throw PSSecurityException) + // - Invoke method + string targetName = methodInfo.ReflectedType?.FullName ?? string.Empty; + expr = Expression.Block( + Expression.Call( + CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation, + Expression.Constant(targetName), + Expression.Constant(name), + Expression.NewArrayInit( + typeof(object), + args.Select(static e => e.Expression.Cast(typeof(object))))), + expr); + } + // If 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 diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index 8d50fcf5f1a..62468dcfc3f 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -3546,4 +3546,69 @@ internal static object[] GetSlice(IList list, int startIndex) return result; } } + + internal static class MemberInvocationLoggingOps + { + private static readonly Lazy DumpLogAMSIContent = new Lazy( + () => { + object result = Environment.GetEnvironmentVariable("__PSDumpAMSILogContent"); + if (result != null && LanguagePrimitives.TryConvertTo(result, out int value)) + { + return value == 1; + } + return false; + } + ); + + 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 = args[i] is null ? "null" : args[i].ToString(); + + if (i > 0) + { + argsBuilder.Append(", "); + } + + argsBuilder.Append($"<{value}>"); + } + + string content = $"<{targetName}>.{name}({argsBuilder})"; + + if (DumpLogAMSIContent.Value) + { + Console.WriteLine("\n=== Amsi notification report content ==="); + Console.WriteLine(content); + } + + var success = AmsiUtils.ReportContent( + name: contentName, + content: content); + + if (DumpLogAMSIContent.Value) + { + Console.WriteLine($"=== Amsi notification report success: {success} ==="); + } + } + catch (PSSecurityException) + { + // ReportContent() will throw PSSecurityException if AMSI detects malware, which + // must be propagated. + throw; + } + catch (Exception ex) + { + if (DumpLogAMSIContent.Value) + { + Console.WriteLine($"!!! Amsi notification report exception: {ex} !!!"); + } + } + } + } } diff --git a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs index fc1dba4aed8..efa604eb21e 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -324,13 +324,11 @@ internal static PowerShell Convert(ScriptBlockAst body, /// Scriptblock to search. /// True when input is trusted. /// Execution context. - /// List of foreach command names and aliases. /// Dictionary of using variable map. internal static Dictionary GetUsingValuesForEachParallel( ScriptBlock scriptBlock, bool isTrustedInput, - ExecutionContext context, - string[] foreachNames) + ExecutionContext context) { // Using variables for Foreach-Object -Parallel use are restricted to be within the // Foreach-Object -Parallel call scope. This will filter the using variable map to variables @@ -350,7 +348,7 @@ internal static Dictionary GetUsingValuesForEachParallel( for (int i = 0; i < usingAsts.Count; ++i) { usingAst = (UsingExpressionAst)usingAsts[i]; - if (IsInForeachParallelCallingScope(usingAst, foreachNames)) + if (IsInForeachParallelCallingScope(scriptBlock.Ast, usingAst)) { var value = Compiler.GetExpressionValue(usingAst.SubExpression, isTrustedInput, context); string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst); @@ -382,17 +380,61 @@ internal static Dictionary GetUsingValuesForEachParallel( return usingValueMap; } + // List of Foreach-Object command names and aliases. + // TODO: Look into using SessionState.Internal.GetAliasTable() to find all user created aliases. + // But update Alias command logic to maintain reverse table that lists all aliases mapping + // to a single command definition, for performance. + private static readonly string[] forEachNames = new string[] + { + "ForEach-Object", + "foreach", + "%" + }; + + private static bool FindForEachInCommand(CommandAst commandAst) + { + // Command name is always the first element in the CommandAst. + // e.g., 'foreach -parallel {}' + var commandNameElement = (commandAst.CommandElements.Count > 0) ? commandAst.CommandElements[0] : null; + if (commandNameElement is StringConstantExpressionAst commandName) + { + bool found = false; + foreach (var foreachName in forEachNames) + { + if (commandName.Value.Equals(foreachName, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + } + + if (found) + { + // Verify this is foreach-object with parallel parameter set. + var bindingResult = StaticParameterBinder.BindCommand(commandAst); + if (bindingResult.BoundParameters.ContainsKey("Parallel")) + { + return true; + } + } + } + + return false; + } + /// /// Walks the using Ast to verify it is used within a foreach-object -parallel command /// and parameter set scope, and not from within a nested foreach-object -parallel call. /// + /// Scriptblock Ast containing this using Ast /// Using Ast to check. - /// List of foreach-object command names. /// True if using expression is in current call scope. private static bool IsInForeachParallelCallingScope( - UsingExpressionAst usingAst, - string[] foreachNames) + Ast scriptblockAst, + UsingExpressionAst usingAst) { + Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null."); + /* Example: $Test1 = "Hello" @@ -405,54 +447,23 @@ private static bool IsInForeachParallelCallingScope( } } */ - Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null."); // Search up the parent Ast chain for 'Foreach-Object -Parallel' commands. Ast currentParent = usingAst.Parent; - int foreachNestedCount = 0; - while (currentParent != null) + while (currentParent != scriptblockAst) { // Look for Foreach-Object outer commands - if (currentParent is CommandAst commandAst) + if (currentParent is CommandAst commandAst && + FindForEachInCommand(commandAst)) { - foreach (var commandElement in commandAst.CommandElements) - { - if (commandElement is StringConstantExpressionAst commandName) - { - bool found = false; - foreach (var foreachName in foreachNames) - { - if (commandName.Value.Equals(foreachName, StringComparison.OrdinalIgnoreCase)) - { - found = true; - break; - } - } - - if (found) - { - // Verify this is foreach-object with parallel parameter set. - var bindingResult = StaticParameterBinder.BindCommand(commandAst); - if (bindingResult.BoundParameters.ContainsKey("Parallel")) - { - foreachNestedCount++; - break; - } - } - } - } - } - - if (foreachNestedCount > 1) - { - // This using expression Ast is outside the original calling scope. + // Using Ast is outside the invoking foreach scope. return false; } currentParent = currentParent.Parent; } - return foreachNestedCount == 1; + return true; } /// diff --git a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs index 49d1e67e5d9..af410e4bd6d 100644 --- a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs +++ b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs @@ -166,22 +166,22 @@ private void HandleProgressChanged(object sender, UpdatableHelpProgressEventArgs /// /// Static constructor /// - /// NOTE: FWLinks for core PowerShell modules are needed since they get loaded as snapins in a Remoting Endpoint. + /// NOTE: HelpInfoUri for core PowerShell modules are needed since they get loaded as snapins in a Remoting Endpoint. /// When we moved to modules in V3, we were not able to make this change as it was a risky change to make at that time. /// static UpdatableHelpCommandBase() { s_metadataCache = new Dictionary(StringComparer.OrdinalIgnoreCase); - // TODO: assign real TechNet addresses + // NOTE: The HelpInfoUri must be updated with each release. - s_metadataCache.Add("Microsoft.PowerShell.Diagnostics", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Core", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Host", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Management", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.PowerShell.Security", "https://aka.ms/powershell71-help"); - s_metadataCache.Add("Microsoft.WSMan.Management", "https://aka.ms/powershell71-help"); + s_metadataCache.Add("Microsoft.PowerShell.Diagnostics", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.PowerShell.Core", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.PowerShell.Host", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.PowerShell.Management", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.PowerShell.Security", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.WSMan.Management", "https://aka.ms/powershell73-help"); } /// diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 37229a18db4..1e3ab21ccaf 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -8260,7 +8260,7 @@ internal static bool IsReparsePointLikeSymlink(FileSystemInfo fileInfo) WIN32_FIND_DATA data = default; string fullPath = Path.TrimEndingDirectorySeparator(fileInfo.FullName); - if (fullPath.Length > MAX_PATH) + if (fullPath.Length >= MAX_PATH) { fullPath = PathUtils.EnsureExtendedPrefix(fullPath); } diff --git a/src/System.Management.Automation/resources/CommandBaseStrings.resx b/src/System.Management.Automation/resources/CommandBaseStrings.resx index dc2dd4bbbf5..abb918adb16 100644 --- a/src/System.Management.Automation/resources/CommandBaseStrings.resx +++ b/src/System.Management.Automation/resources/CommandBaseStrings.resx @@ -222,4 +222,10 @@ 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. + diff --git a/src/System.Management.Automation/resources/DiscoveryExceptions.resx b/src/System.Management.Automation/resources/DiscoveryExceptions.resx index d3923969e8d..2d8445deb4f 100644 --- a/src/System.Management.Automation/resources/DiscoveryExceptions.resx +++ b/src/System.Management.Automation/resources/DiscoveryExceptions.resx @@ -206,6 +206,10 @@ The #requires statement must be in one of the following formats: The '{0}' command was found in the module '{1}', but the module could not be loaded. For more information, run 'Import-Module {1}'. + + The '{0}' command was found in the module '{1}', but the module could not be loaded due to the following error: [{2}] +For more information, run 'Import-Module {1}'. + The module '{0}' could not be loaded. For more information, run 'Import-Module {0}'. diff --git a/src/System.Management.Automation/resources/Modules.resx b/src/System.Management.Automation/resources/Modules.resx index 345811502b6..2e50a3c5317 100644 --- a/src/System.Management.Automation/resources/Modules.resx +++ b/src/System.Management.Automation/resources/Modules.resx @@ -624,4 +624,7 @@ Cannot create new module while the session is in ConstrainedLanguage mode. + + Cannot find the built-in module '{0}' that is compatible with the 'Core' edition. Please make sure the PowerShell built-in modules are available. They usually come with the PowerShell package under the $PSHOME module path, and are required for PowerShell to function properly. + diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index 441228c9c44..680e6d1a6b5 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -837,8 +837,7 @@ Note that 'Start-Job' is not supported by design in scenarios where PowerShell i A {1} job source adapter threw an exception with the following message: {0} - The value {0} is not valid for the {1} parameter. The available values are 2.0, 3.0, 4.0, 5.0, 5.1. - {StrContains="2.0"} {StrContains="3.0"} + The value {0} is not valid for the {1} parameter. The only allowed value is 5.1. The Wait and Keep parameters cannot be used together in the same command. @@ -846,8 +845,8 @@ Note that 'Start-Job' is not supported by design in scenarios where PowerShell i The WriteEvents parameter cannot be used without the Wait parameter. - - PowerShell {0} is not installed. Install PowerShell {0}, and then try again. + + PowerShell remoting endpoint versioning is not supported on PowerShell Core. The following type cannot be instantiated because its constructor is not public: {0}. diff --git a/src/System.Management.Automation/security/SecurityManager.cs b/src/System.Management.Automation/security/SecurityManager.cs index c82b03b99c7..25fb544507b 100644 --- a/src/System.Management.Automation/security/SecurityManager.cs +++ b/src/System.Management.Automation/security/SecurityManager.cs @@ -328,9 +328,10 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r if (string.Equals(fi.Extension, ".ps1xml", StringComparison.OrdinalIgnoreCase)) { string[] trustedDirectories = new string[] - { Platform.GetFolderPath(Environment.SpecialFolder.System), - Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles) - }; + { + Environment.GetFolderPath(Environment.SpecialFolder.System), + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + }; foreach (string trustedDirectory in trustedDirectories) { diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index e41b64db625..7b5087411ec 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -1383,7 +1383,10 @@ internal static AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string #endif } - internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, string sourceMetadata, bool warmUp) + internal static AmsiNativeMethods.AMSI_RESULT WinScanContent( + string content, + string sourceMetadata, + bool warmUp) { if (string.IsNullOrEmpty(sourceMetadata)) { @@ -1414,33 +1417,9 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str 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; - } - } - - // 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) - { - hr = AmsiNativeMethods.AmsiOpenSession(s_amsiContext, ref s_amsiSession); - AmsiInitialized = true; - - if (!Utils.Succeeded(hr)) - { - s_amsiInitFailed = true; - return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; - } + return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; } if (warmUp) @@ -1453,6 +1432,7 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN; // Run AMSI content scan + int hr; unsafe { fixed (char* buffer = content) @@ -1484,6 +1464,123 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str } } + /// + /// Reports provided content to AMSI (Antimalware Scan Interface). + /// + /// Name of content being reported. + /// Content being reported. + /// True if content was successfully reported. + internal static bool ReportContent( + string name, + string content) + { +#if UNIX + return false; +#else + return WinReportContent(name, content); +#endif + } + + private static bool WinReportContent( + string name, + string content) + { + if (string.IsNullOrEmpty(name) || + string.IsNullOrEmpty(content) || + s_amsiInitFailed || + s_amsiNotifyFailed) + { + return false; + } + + lock (s_amsiLockObject) + { + if (s_amsiNotifyFailed) + { + return false; + } + + try + { + if (!CheckAmsiInit()) + { + return false; + } + + int hr; + AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; + unsafe + { + fixed (char* buffer = content) + { + var buffPtr = new IntPtr(buffer); + hr = AmsiNativeMethods.AmsiNotifyOperation( + amsiContext: s_amsiContext, + buffer: buffPtr, + length: (uint)(content.Length * sizeof(char)), + contentName: name, + ref result); + } + } + + if (Utils.Succeeded(hr)) + { + if (result == AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED) + { + // If malware is detected, throw to prevent method invoke expression from running. + throw new PSSecurityException(ParserStrings.ScriptContainedMaliciousContent); + } + + return true; + } + + return false; + } + catch (DllNotFoundException) + { + s_amsiNotifyFailed = true; + return false; + } + catch (System.EntryPointNotFoundException) + { + s_amsiNotifyFailed = true; + return false; + } + } + } + + private static bool CheckAmsiInit() + { + // Initialize AntiMalware Scan Interface, if not already initialized. + // If we failed to initialize previously, just return the neutral result ("AMSI_RESULT_NOT_DETECTED") + if (s_amsiContext == IntPtr.Zero) + { + int hr = Init(); + + if (!Utils.Succeeded(hr)) + { + s_amsiInitFailed = true; + 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)) + { + s_amsiInitFailed = true; + return false; + } + } + + return true; + } + internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) { if (AmsiInitialized && !AmsiUninitializeCalled) @@ -1499,6 +1596,7 @@ internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) private static IntPtr s_amsiSession = IntPtr.Zero; private static bool s_amsiInitFailed = false; + private static bool s_amsiNotifyFailed = false; private static readonly object s_amsiLockObject = new object(); /// @@ -1623,8 +1721,27 @@ internal static extern int AmsiInitialize( [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] [DllImportAttribute("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, + [InAttribute()][MarshalAsAttribute(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* + [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] + [DllImportAttribute("amsi.dll", EntryPoint = "AmsiNotifyOperation", CallingConvention = CallingConvention.StdCall)] + internal static extern int AmsiNotifyOperation( + System.IntPtr amsiContext, + System.IntPtr buffer, + uint length, + [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName, + ref AMSI_RESULT result); /// Return Type: HRESULT->LONG->int ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index 574f42eace3..cf1fdf5b956 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -83,31 +83,6 @@ internal static bool IsRunningOnProcessorArchitectureARM() 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() { IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties(); diff --git a/test/packaging/windows/msi.tests.ps1 b/test/packaging/windows/msi.tests.ps1 index 9d1c218c4c0..0ee34ca482c 100644 --- a/test/packaging/windows/msi.tests.ps1 +++ b/test/packaging/windows/msi.tests.ps1 @@ -39,7 +39,7 @@ Describe -Name "Windows MSI" -Fixture { } function Get-UseMU { - $useMu = 0 + $useMu = $null $key = 'HKLM:\SOFTWARE\Microsoft\PowerShellCore\' if ($runtime -like '*x86*') { $key = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\PowerShellCore\' @@ -49,6 +49,25 @@ Describe -Name "Windows MSI" -Fixture { $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 } @@ -185,6 +204,15 @@ Describe -Name "Windows MSI" -Fixture { } Context "Add Path disabled" { + BeforeAll { + Set-UseMU -Value 0 + } + + It "UseMU should be 0 before install" -Skip:(!(Test-Elevated)) { + $useMu = Get-UseMU + $useMu | Should -Be 0 + } + It "MSI should install without error" -Skip:(!(Test-Elevated)) { { Invoke-MsiExec -Install -MsiPath $msiX64Path -Properties @{ADD_PATH = 0; USE_MU = 1; ENABLE_MU = 1} @@ -213,6 +241,15 @@ Describe -Name "Windows MSI" -Fixture { } 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} diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index 5c903f53745..1798a71939f 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -414,7 +414,7 @@ export $envVarName='$guid' It "text output" { # Join (multiple lines) and remove whitespace (we don't care about spacing) to verify we converted to string (by generating a table) - -join (& $powershell -noprofile -outputFormat text { [PSCustomObject]@{X=10;Y=20} }) -replace "\s","" | Should -Be "XY--1020" + -join (& $powershell -noprofile -outputFormat text { $PSStyle.OutputRendering = 'PlainText'; [PSCustomObject]@{X=10;Y=20} }) -replace "\s","" | Should -Be "XY--1020" } It "errors are in text if error is redirected, encoded command, non-interactive, and outputformat specified" { @@ -491,7 +491,15 @@ export $envVarName='$guid' } Context "Redirected standard input for 'interactive' use" { - $nl = [Environment]::Newline + BeforeAll { + $nl = [Environment]::Newline + $oldColor = $env:NO_COLOR + $env:NO_COLOR = 1 + } + + AfterAll { + $env:NO_COLOR = $oldColor + } # All of the following tests replace the prompt (either via an initial command or interactively) # so that we can read StandardOutput and reliably know exactly what the prompt is. @@ -586,6 +594,8 @@ foo It "Redirected input w/ nested prompt" -Pending:($IsWindows) { $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS' + ('>'*(`$NestedPromptLevel+1)) + ' ' }""" -RedirectStdIn $process = RunPowerShell $si + $process.StandardInput.Write("`$PSStyle.OutputRendering='plaintext'`n") + $null = $process.StandardOutput.ReadLine() $process.StandardInput.Write("`$Host.EnterNestedPrompt()`n") $process.StandardOutput.ReadLine() | Should -Be "PS> `$Host.EnterNestedPrompt()" $process.StandardInput.Write("exit`n") @@ -664,6 +674,20 @@ namespace StackTest { It "Should start if HOME is not defined" -Skip:($IsWindows) { bash -c "unset HOME;$powershell -c '1+1'" | Should -BeExactly 2 } + + It "Same user should use the same temporary HOME directory for different sessions" -Skip:($IsWindows) { + $results = bash -c @" +unset HOME; +$powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::DEFAULT)'; +$powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::DEFAULT)'; +"@ + $results | Should -HaveCount 2 + $results[0] | Should -BeExactly $results[1] + + $tempHomeName = "pwsh-{0}-98288ff9-5712-4a14-9a11-23693b9cd91a" -f [System.Environment]::UserName + $defaultPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$tempHomeName/.config/powershell" + $results[0] | Should -BeExactly $defaultPath + } } Context "PATH environment variable" { @@ -833,6 +857,64 @@ namespace StackTest { $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter } } + + Context "Startup banner text tests" -Tag Slow { + BeforeAll { + $outputPath = "Temp:\StartupBannerTest-Output-${Pid}.txt" + $inputPath = "Temp:\StartupBannerTest-Input.txt" + "exit" > $inputPath + + # Not testing update notification banner text here + $oldPowerShellUpdateCheck = $env:POWERSHELL_UPDATECHECK + $env:POWERSHELL_UPDATECHECK = "Off" + + # Set TERM to "dumb" to avoid DECCKM codes in the output + $oldTERM = $env:TERM + $env:TERM = "dumb" + + $escPwd = [regex]::Escape($pwd) + $expectedPromptPattern = "^PS ${escPwd}> exit`$" + + $spArgs = @{ + FilePath = $powershell + ArgumentList = @("-NoProfile") + RedirectStandardInput = $inputPath + RedirectStandardOutput = $outputPath + WorkingDirectory = $pwd + PassThru = $true + NoNewWindow = $true + UseNewEnvironment = $false + } + } + AfterAll { + $env:TERM = $oldTERM + $env:POWERSHELL_UPDATECHECK = $oldPowerShellUpdateCheck + + Remove-Item $inputPath -Force -ErrorAction Ignore + Remove-Item $outputPath -Force -ErrorAction Ignore + } + BeforeEach { + Remove-Item $outputPath -Force -ErrorAction Ignore + } + It "Displays expected startup banner text by default" { + $process = Start-Process @spArgs + Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue + + $out = @(Get-Content $outputPath) + $out.Count | Should -Be 2 + $out[0] | Should -BeExactly "PowerShell $($PSVersionTable.GitCommitId)" + $out[1] | Should -MatchExactly $expectedPromptPattern + } + It "Displays only the prompt with -NoLogo" { + $spArgs["ArgumentList"] += "-NoLogo" + $process = Start-Process @spArgs + Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue + + $out = @(Get-Content $outputPath) + $out.Count | Should -Be 1 + $out[0] | Should -MatchExactly $expectedPromptPattern + } + } } Describe "WindowStyle argument" -Tag Feature { diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 67aa4bb5907..6513bd87253 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1097,6 +1097,14 @@ Describe "TabCompletion" -Tags CI { $entry.CompletionText | Should -BeExactly "Mandatory" } + 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 "Test completion with line continuation" { $inputStr = @' dir -Recurse ` diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 index 0c7b7f9d512..c8e3c75cb17 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 @@ -171,7 +171,7 @@ Describe "Will error correctly if an attempt to set variable to improper value" } } -foreach ( $argumentListValue in "Standard","Legacy" ) { +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 @@ -263,6 +263,28 @@ foreach ( $argumentListValue in "Standard","Legacy" ) { $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>' + } + + } } } Describe 'PSPath to native commands' -tags "CI" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 index 72a7c9eddb1..f9094254ec3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/CompatiblePSEditions.Module.Tests.ps1 @@ -11,7 +11,7 @@ function Add-ModulePath if ($Prepend) { - $env:PSModulePAth = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath + $env:PSModulePath = $Path + [System.IO.Path]::PathSeparator + $env:PSModulePath } else { @@ -131,6 +131,22 @@ function New-TestNestedModule [scriptblock]::Create($newManifestCmd).Invoke() } +function Get-DesktopModuleToUse { + $system32Path = "$env:windir\system32\WindowsPowerShell\v1.0\Modules" + $persistentMemoryModule = "PersistentMemory" + $remoteDesktopModule = "RemoteDesktop" + + if (Test-Path -PathType Container "$system32Path\$persistentMemoryModule") { + return $persistentMemoryModule + } elseif (Test-Path -PathType Container "$system32Path\$remoteDesktopModule") { + return $remoteDesktopModule + } else { + return $null + } +} + +$desktopModuleToUse = Get-DesktopModuleToUse + Describe "Get-Module with CompatiblePSEditions-checked paths" -Tag "CI" { BeforeAll { @@ -643,6 +659,37 @@ Describe "Additional tests for Import-Module with WinCompat" -Tag "Feature" { '{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["' + $ModuleName2 + '"]}' | Out-File -Force $ConfigPath & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('TestWindowsPowerShellPSHomeLocation', `'$basePath`');Test-${ModuleName2}PSEdition;Test-$ModuleName2" | Should -Be @('Desktop','Core') } + + It "NoClobber WinCompat list in powershell.config is a Desktop-edition module" { + if (-not $desktopModuleToUse) { + throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.' + } + + ## The 'Desktop' edition module 'PersistentMemory' (available on Windows Client) or 'RemoteDesktop' (available on Windows Server) should not be imported twice. + $ConfigPath = Join-Path $TestDrive 'powershell.config.json' +@" +{"Microsoft.PowerShell:ExecutionPolicy": "RemoteSigned", "WindowsPowerShellCompatibilityNoClobberModuleList": ["$desktopModuleToUse"]} +"@ | Out-File -Force $ConfigPath + $env:PSModulePath = '' + + ## The desktop-edition module is listed in the no-clobber list, so we will first try loading a core-edition + ## compatible version of the module before loading the remote one. The 'system32' module path will be skipped + ## in this attempt, which is by-design. + ## If we don't skip the 'system32' module path in this loading attempt, the desktop-edition module will be + ## imported twice as a remote module, and then 'Remove-Module' won't close the WinCompat session. + $script = @" +Import-Module $desktopModuleToUse -UseWindowsPowerShell -WarningAction Ignore +Get-Module $desktopModuleToUse | ForEach-Object { `$_.ModuleType.ToString() } +(Get-PSSession | Measure-Object).Count +Remove-Module $desktopModuleToUse +(Get-PSSession | Measure-Object).Count +"@ + $scriptBlock = [scriptblock]::Create($script) + $results = & $pwsh -NoProfile -NonInteractive -settingsFile $ConfigPath -c $scriptBlock + $results[0] | Should -BeExactly 'Script' + $results[1] | Should -BeExactly 1 + $results[2] | Should -BeExactly 0 + } } Context "Tests around PSModulePath in WinCompat process" { @@ -1343,3 +1390,123 @@ Describe "Import-Module nested module behaviour with Edition checking" -Tag "Fea } } } + +Describe "WinCompat importing should check availablity of built-in modules" -Tag "CI" { + BeforeAll { + if (-not $IsWindows ) { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + + ## Copy the current PowerShell instance to a temp location + $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) "WinCompat" + $pwshDir = Join-Path $tempDir "pwsh" + $moduleDir = Join-Path $tempDir "Modules" + $savedModulePath = $env:PSModulePath + + if (Test-Path $tempDir) { + Remove-Item $tempDir -Recurse -Force + } + + Write-Host "Making a copy of the running PowerShell instance ..." -ForegroundColor Yellow + Copy-Item $PSHOME $pwshDir -Recurse -Force + Move-Item $pwshDir\Modules $moduleDir -Force + Write-Host "-- Done copying!" -ForegroundColor Yellow + } + + AfterAll { + if (-not $IsWindows) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + + $env:PSModulePath = $savedModulePath + Remove-Item $tempDir -Recurse -Force + } + + It "Missing built-in modules will trigger error instead of loading the non-compatible ones in System32 directory. Running ''" -TestCases @( + @{ + Command = 'Start-Transcript'; + FullyQualifiedErrorId = "CouldNotAutoloadMatchingModule"; + ExceptionMessage = "*'Start-Transcript'*'Microsoft.PowerShell.Host'*'Microsoft.PowerShell.Host'*'Core'*`$PSHOME*'Import-Module Microsoft.PowerShell.Host'*" + } + @{ + Command = 'Import-Module Microsoft.PowerShell.Host'; + FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand" + ExceptionMessage = "*'Microsoft.PowerShell.Host'*'Core'*`$PSHOME*" + } + @{ + Command = 'Import-Module CimCmdlets' + FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand" + ExceptionMessage = "*'CimCmdlets'*'Core'*`$PSHOME*" + } + @{ + Command = 'Import-Module Microsoft.PowerShell.Utility' + FullyQualifiedErrorId = "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportModuleCommand" + ExceptionMessage = "*'Microsoft.PowerShell.Utility'*'Core'*`$PSHOME*" + } + ) { + param( + $Command, + $FullyQualifiedErrorId, + $ExceptionMessage + ) + + $template = @' + try {{ + {0} + }} catch {{ + $_.FullyQualifiedErrorId + $_.Exception.Message + }} +'@ + $env:PSModulePath = '' + $script = $template -f $Command + $scriptBlock = [scriptblock]::Create($script) + + $result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock + $result | Should -HaveCount 2 + $result[0] | Should -BeExactly $FullyQualifiedErrorId + $result[1] | Should -BeLike $ExceptionMessage + } + + It "Attempt to load a 'Desktop' edition module should fail because 'Export-PSSession' cannot be found" { + if (-not $desktopModuleToUse) { + throw 'Neither the "PersistentMemory" module nor the "RemoteDesktop" module is available. Please check and use a desktop-edition module that is under the System32 module path.' + } + + $script = @" + try { + Import-Module $desktopModuleToUse -ErrorAction Stop + } catch { + `$_.FullyQualifiedErrorId + `$_.Exception.Message + } +"@ + $env:PSModulePath = '' + $scriptBlock = [scriptblock]::Create($script) + $result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c $scriptBlock + $result | Should -HaveCount 2 + $result[0] | Should -BeExactly "CommandNotFoundException,Microsoft.PowerShell.Commands.ImportModuleCommand" + $result[1] | Should -BeLike "*'$desktopModuleToUse'*'Export-PSSession'*'Microsoft.PowerShell.Utility'*" + } + + It "When built-in modules are available but not in `$PSHOME module path, things should work" { + $env:PSModulePath = '' + $result = & "$pwshDir\pwsh.exe" -NoProfile -NonInteractive -c @" + `$env:PSModulePath += ';$moduleDir' + Import-Module Microsoft.PowerShell.Utility -UseWindowsPowerShell -WarningAction Ignore + Get-Module Microsoft.PowerShell.Utility | ForEach-Object ModuleType + Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Manifest' | ForEach-Object Path + Get-Module Microsoft.PowerShell.Utility | Where-Object ModuleType -eq 'Script' | ForEach-Object { `$_.ExportedCommands.Keys } +"@ + $result | Should -HaveCount 6 + $result[0] | Should -BeExactly 'Manifest' + $result[1] | Should -BeExactly 'Script' + $result[2] | Should -BeExactly "$moduleDir\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1" + $result[3] | Should -BeExactly 'Convert-String' + $result[4] | Should -BeExactly 'ConvertFrom-String' + $result[5] | Should -BeExactly 'CFS' + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 index e586d047804..a4898b1e1ad 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Enter-PSHostProcess.Tests.ps1 @@ -68,6 +68,15 @@ Exit-PSHostProcess Describe "Enter-PSHostProcess tests" -Tag Feature { Context "By Process Id" { + BeforeAll { + $oldColor = $env:NO_COLOR + $env:NO_COLOR = 1 + } + + AfterAll { + $env:NO_COLOR = $oldColor + } + BeforeEach { # Start a normal job where the first thing it does is return $PID. After that, spin forever. # We will use this job as the target process for Enter-PSHostProcess @@ -181,6 +190,15 @@ Describe "Enter-PSHostProcess tests" -Tag Feature { Context "By CustomPipeName" { + BeforeAll { + $oldColor = $env:NO_COLOR + $env:NO_COLOR = 1 + } + + AfterAll { + $env:NO_COLOR = $oldColor + } + It "Can enter, exit, and re-enter using CustomPipeName" { $pipeName = [System.IO.Path]::GetRandomFileName() $pipePath = Get-PipePath -PipeName $pipeName diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1 new file mode 100644 index 00000000000..bd3c87e0f5c --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Exec.Tests.ps1 @@ -0,0 +1,74 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Switch-Process tests for Unix' -Tags 'CI' { + BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + if (-not [ExperimentalFeature]::IsEnabled('PSExec') -or $IsWindows) + { + $PSDefaultParameterValues['It:Skip'] = $true + return + } + } + + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + + It 'Exec alias should map to Switch-Process' { + $alias = Get-Command exec + $alias | Should -BeOfType [System.Management.Automation.AliasInfo] + $alias.Definition | Should -BeExactly 'Switch-Process' + } + + It 'Exec by itself does nothing' { + exec | Should -BeNullOrEmpty + } + + It 'Exec given a cmdlet should fail' { + { exec Get-Command } | Should -Throw -ErrorId 'CommandNotFound,Microsoft.PowerShell.Commands.SwitchProcessCommand' + } + + It 'Exec given an exe should work' { + $id, $uname = pwsh -noprofile -noexit -outputformat text -command { $pid; exec uname } + Get-Process -Id $id -ErrorAction Ignore| Should -BeNullOrEmpty + $uname | Should -BeExactly (uname) + } + + It 'Exec given an exe and arguments should work' { + $id, $uname = pwsh -noprofile -noexit -outputformat text -command { $pid; exec uname -a } + Get-Process -Id $id -ErrorAction Ignore| Should -BeNullOrEmpty + $uname | Should -BeExactly (uname -a) + } + + It 'Exec will replace the process' { + $sleep = Get-Command sleep -CommandType Application | Select-Object -First 1 + $p = Start-Process pwsh -ArgumentList "-noprofile -command exec $($sleep.Source) 90" -PassThru + Wait-UntilTrue { + ($p | Get-Process).Name -eq 'sleep' + } -timeout 60000 -interval 100 | Should -BeTrue + } +} + +Describe 'Switch-Process for Windows' { + BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + if (-not $IsWindows) + { + $PSDefaultParameterValues['It:Skip'] = $true + return + } + } + + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + + It 'Switch-Process should not be available' { + Get-Command -Name Switch-Process -ErrorAction Ignore | Should -BeNullOrEmpty + } + + It 'Exec alias should not be available' { + Get-Alias -Name exec -ErrorAction Ignore | Should -BeNullOrEmpty + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1 new file mode 100644 index 00000000000..1765e5d8217 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Invoke-Command.Tests.ps1 @@ -0,0 +1,41 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Invoke-Command" -Tags "CI" { + Context "StrictMode tests" { + BeforeAll { + $skipTest = !($EnabledExperimentalFeatures -contains "PSStrictModeAssignment"); + If (Test-Path Variable:InvokeCommand__Test) { + Remove-Item Variable:InvokeCommand__Test + } + } + + It "Setting -StrictMode parameter with uninitialized variable throws error" -skip:$skipTest { + { Invoke-Command -StrictMode 3.0 {$InvokeCommand__Test} } | Should -Throw -ErrorId 'VariableIsUndefined' + } + + It "Setting -StrictMode parameter with initialized variable does not throw error" -skip:$skipTest { + $InvokeCommand__Test = 'Something' + Invoke-Command -StrictMode 3.0 {$InvokeCommand__Test} | Should -Be 'Something' + Remove-Item Variable:InvokeCommand__Test + } + + It "-StrictMode parameter sets StrictMode back to original state after process completes" -skip:$skipTest { + { Invoke-Command -StrictMode 3.0 {$InvokeCommand__Test} } | Should -Throw -ErrorId 'VariableIsUndefined' + { Invoke-Command {$InvokeCommand__Test} } | Should -Not -Throw + } + + It "-StrictMode parameter works on piped input" -skip:$skipTest { + "There" | Invoke-Command -ScriptBlock { "Hello $input" } -StrictMode 3.0 | Should -Be 'Hello There' + { "There" | Invoke-Command -ScriptBlock { "Hello $InvokeCommand__Test" } -StrictMode 3.0 } | Should -Throw -ErrorId 'VariableIsUndefined' + } + + It "-StrictMode latest works" -skip:$skipTest { + { Invoke-Command -StrictMode latest {$InvokeCommand__Test} } | Should -Throw -ErrorId 'VariableIsUndefined' + } + + It "-StrictMode off works" -skip:$skipTest { + { Invoke-Command -StrictMode off {$InvokeCommand__Test} } | Should -Not -Throw + } + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Job.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Job.Tests.ps1 index 06d84230dce..90477267b4e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Job.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Job.Tests.ps1 @@ -243,3 +243,16 @@ Describe "Ampersand background test" -Tag "CI","Slow" { } } } + +Describe "Start-Job with -PSVersion parameter" -Tag "CI" { + + It "Verifies that -PSVersion is not supported except for version 5.1" { + { Start-Job -PSVersion 2.0 } | Should -Throw -ErrorId 'ParameterBindingFailed,Microsoft.PowerShell.Commands.StartJobCommand' + } + + It "Verifies that -PSVersion 5.1 runs the job in a version 5.1 PowerShell session" -Skip:(-not $IsWindows) { + $version = Start-Job -PSVersion 5.1 -ScriptBlock { $PSVersionTable } | Receive-Job -Wait -AutoRemoveJob + $version.PSVersion.Major | Should -Be 5 + $version.PSVersion.Minor | Should -Be 1 + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 index d910f2c265c..a4e9e1e5116 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 @@ -491,13 +491,9 @@ namespace PowershellTestConfigNamespace $Result.Session.UseSharedProcess | Should -Be $UseSharedProcess } - It "Validate Register-PSSessionConfiguration -PSVersion" { + It "Verifies that Register-PSSessionConfiguration -PSVersion parameter is not supported" { - Register-PSSessionConfiguration -Name $TestSessionConfigName -PSVersion 5.1 - $Session = Get-PSSessionConfiguration -Name $TestSessionConfigName - - $Session.Name | Should -Be $TestSessionConfigName - $Session.PSVersion | Should -BeExactly 5.1 + { Register-PSSessionConfiguration -Name $TestSessionConfigName -PSVersion 5.1 } | Should -Throw -ErrorId 'ParameterBindingFailed,Microsoft.PowerShell.Commands.RegisterPSSessionConfigurationCommand' } It "Validate Register-PSSessionConfiguration -startupscript parameter" -Pending { @@ -567,13 +563,9 @@ namespace PowershellTestConfigNamespace $Result.Session.UseSharedProcess | Should -Be $UseSharedProcess } - It "Validate Set-PSSessionConfiguration -PSVersion" { - - Set-PSSessionConfiguration -Name $TestSessionConfigName -PSVersion 5.1 - $Session = (Get-PSSessionConfiguration -Name $TestSessionConfigName) + It "Verifies that Set-PSSessionConfiguration -PSVersion parameter is not supported" { - $Session.Name | Should -Be $TestSessionConfigName - $Session.PSVersion | Should -BeExactly 5.1 + { Set-PSSessionConfiguration -Name $TestSessionConfigName -PSVersion 5.1 } | Should -Throw -ErrorId 'ParameterBindingFailed,Microsoft.PowerShell.Commands.SetPSSessionConfigurationCommand' } It "Validate Set-PSSessionConfiguration -startupscript parameter" -Pending { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 index 0dafa6f31de..5059928f322 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 @@ -6,6 +6,7 @@ Describe "Get-ChildItem" -Tags "CI" { BeforeAll { # Create Test data + $max_Path = 260 $item_a = "a3fe710a-31af-4834-bc29-d0b584589838" $item_B = "B1B691A9-B7B1-4584-AED7-5259511BEEC4" $item_c = "c283d143-2116-4809-bf11-4f7d61613f92" @@ -13,6 +14,9 @@ Describe "Get-ChildItem" -Tags "CI" { $item_E = "EE150FEB-0F21-4AFF-8066-AF59E925810C" $item_F = ".F81D8514-8862-4227-B041-0529B1656A43" $item_G = "5560A62F-74F1-4FAE-9A23-F4EBD90D2676" + $item_H = "5f05ebca-4859-11ec-81d3-0242ac130003" + $item_I = "z" * ($max_Path - $TestDrive.FullName.Length - $item_H.Length - 2) + $item_J = "32d74aae-9054-4fa7-be97-8c806d10e8b9" $null = New-Item -Path $TestDrive -Name $item_a -ItemType "File" -Force $null = New-Item -Path $TestDrive -Name $item_B -ItemType "File" -Force $null = New-Item -Path $TestDrive -Name $item_c -ItemType "File" -Force @@ -20,6 +24,7 @@ Describe "Get-ChildItem" -Tags "CI" { $null = New-Item -Path $TestDrive -Name $item_E -ItemType "Directory" -Force $null = New-Item -Path $TestDrive -Name $item_F -ItemType "File" -Force | ForEach-Object {$_.Attributes = "hidden"} $null = New-Item -Path (Join-Path -Path $TestDrive -ChildPath $item_E) -Name $item_G -ItemType "File" -Force + $null = New-Item -Path $TestDrive\$item_I\$item_H -Name $item_J -ItemType "Directory" -Force $searchRoot = Join-Path $TestDrive -ChildPath "TestPS" $file1 = Join-Path $searchRoot -ChildPath "D1" -AdditionalChildPath "File1.txt" @@ -103,16 +108,17 @@ Describe "Get-ChildItem" -Tags "CI" { It "Should list files in sorted order" { $files = Get-ChildItem -Path $TestDrive $files[0].Name | Should -Be $item_E - $files[1].Name | Should -Be $item_a - $files[2].Name | Should -Be $item_B - $files[3].Name | Should -Be $item_c - $files[4].Name | Should -Be $item_D + $files[1].Name | Should -Be $item_I + $files[2].Name | Should -Be $item_a + $files[3].Name | Should -Be $item_B + $files[4].Name | Should -Be $item_c + $files[5].Name | Should -Be $item_D } It "Should list hidden files as well when 'Force' parameter is used" { $files = Get-ChildItem -Path $TestDrive -Force $files | Should -Not -BeNullOrEmpty - $files.Count | Should -Be 6 + $files.Count | Should -Be 7 $files.Name.Contains($item_F) | Should -BeTrue } @@ -130,22 +136,22 @@ Describe "Get-ChildItem" -Tags "CI" { } It "Should list items in current directory only with depth set to 0" { - (Get-ChildItem -Path $TestDrive -Depth 0).Count | Should -Be 5 - (Get-ChildItem -Path $TestDrive -Depth 0 -Include *).Count | Should -Be 5 - (Get-ChildItem -Path $TestDrive -Depth 0 -Exclude IntentionallyNonexistent).Count | Should -Be 5 + (Get-ChildItem -Path $TestDrive -Depth 0).Count | Should -Be 6 + (Get-ChildItem -Path $TestDrive -Depth 0 -Include *).Count | Should -Be 6 + (Get-ChildItem -Path $TestDrive -Depth 0 -Exclude IntentionallyNonexistent).Count | Should -Be 6 } It "Should return items recursively when using 'Include' or 'Exclude' parameters" { - (Get-ChildItem -Path $TestDrive -Depth 1).Count | Should -Be 6 + (Get-ChildItem -Path $TestDrive -Depth 1).Count | Should -Be 8 (Get-ChildItem -Path $TestDrive -Depth 1 -Include $item_G).Count | Should -Be 1 - (Get-ChildItem -Path $TestDrive -Depth 1 -Exclude $item_a).Count | Should -Be 5 + (Get-ChildItem -Path $TestDrive -Depth 1 -Exclude $item_a).Count | Should -Be 7 } It "Should return items recursively when using 'Include' or 'Exclude' parameters with -LiteralPath" { (Get-ChildItem -LiteralPath $TestDrive -Recurse -Exclude *).Count | Should -Be 0 (Get-ChildItem -LiteralPath $TestDrive -Recurse -Include *.dll).Count | Should -Be (Get-ChildItem $TestDrive -Recurse -Include *.dll).Count (Get-ChildItem -LiteralPath $TestDrive -Depth 1 -Include $item_G).Count | Should -Be 1 - (Get-ChildItem -LiteralPath $TestDrive -Depth 1 -Exclude $item_a).Count | Should -Be 5 + (Get-ChildItem -LiteralPath $TestDrive -Depth 1 -Exclude $item_a).Count | Should -Be 7 } It "get-childitem path wildcard - " -TestCases $PathWildCardTestCases { @@ -207,6 +213,10 @@ Describe "Get-ChildItem" -Tags "CI" { Set-Location $oldLocation } } + + It "Should list the folder present when path length equal to MAX_PATH" { + (Get-ChildItem -Path TestDrive:\$item_I -Recurse -Force).Name.Length | Should -BeGreaterThan 0 + } } Context 'Env: Provider' { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 index 994d71601a7..fb7438608d1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Foreach-Object-Parallel.Tests.ps1 @@ -87,6 +87,185 @@ Describe 'ForEach-Object -Parallel Basic Tests' -Tags 'CI' { $usingErrors[0].FullyQualifiedErrorId | Should -BeExactly 'UsingVariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand' } + It 'Verifies using variables with passed-in script block object' { + + $var = "Hello" + $varArray = "Hello","There" + $sBlock = [scriptblock]::Create('$using:var; $using:varArray[1]') + $result = 1..1 | ForEach-Object -Parallel $sBlock + $result[0] | Should -BeExactly $var + $result[1] | Should -BeExactly $varArray[1] + } + + It 'Verifies using variables with passed-in {} script block object' { + + $var = "Hello" + $varArray = "Hello","There" + $sBlock = { $using:var; $using:varArray[1] } + $result = 1..1 | ForEach-Object -Parallel $sBlock + $result[0] | Should -BeExactly $var + $result[1] | Should -BeExactly $varArray[1] + } + + It 'Verifies in scope using same-name variables in nested calls for passed-in script block objects' { + + $Test1 = "Test1" + $sBlock = [scriptblock]::Create(@' + $using:Test1 + $Test2 = "Test2" + $sBlock2 = [scriptblock]::Create('$using:Test2') + 1..2 | ForEach-Object -Parallel $sBlock2 +'@) + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using same-name variables in nested calls for passed-in {} script block objects' { + + $Test1 = "Test1" + $sBlock = { + $using:Test1 + $Test2 = "Test2" + $sBlock2 = [scriptblock]::Create('$using:Test2') + 1..2 | ForEach-Object -Parallel $sBlock2 + } + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using same-name variables in nested calls for mixed script block objects' { + + $Test1 = "Test1" + $sBlock = [scriptblock]::Create(@' + $using:Test1 + $Test2 = "Test2" + 1..2 | ForEach-Object -Parallel { $using:Test2 } +'@) + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using same-name variables in nested calls for mixed script block {} objects' { + + $Test1 = "Test1" + $sBlock = { + $using:Test1 + $Test2 = "Test2" + 1..2 | ForEach-Object -Parallel { $using:Test2 } + } + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using different-name variables in nested calls for passed-in script block objects' { + + $Test1 = "Test1" + $sBlock = [scriptblock]::Create(@' + $using:Test1 + $Test2 = "Test2" + $sBlock2 = [scriptblock]::Create('$using:Test2') + 1..2 | ForEach-Object -Parallel $sBlock2 +'@) + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using different-name variables in nested calls for passed-in script {} block objects' { + + $Test1 = "Test1" + $sBlock = { + $using:Test1 + $Test2 = "Test2" + $sBlock2 = [scriptblock]::Create('$using:Test2') + 1..2 | ForEach-Object -Parallel $sBlock2 + } + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using different-name variables in nested calls for mixed script block objects' { + + $Test1 = "Test1" + $sBlock = [scriptblock]::Create(@' + $using:Test1 + $Test2 = "Test2" + 1..2 | ForEach-Object -Parallel { $using:Test2 } +'@) + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies in scope using different-name variables in nested calls for mixed script block {} objects' { + + $Test1 = "Test1" + $sBlock = { + $using:Test1 + $Test2 = "Test2" + 1..2 | ForEach-Object -Parallel { $using:Test2 } + } + $results = 1..2 | ForEach-Object -Parallel $sBlock + $results.Count | Should -BeExactly 6 + $groups = $results | Group-Object -AsHashTable + $groups['Test1'].Count | Should -BeExactly 2 + $groups['Test2'].Count | Should -BeExactly 4 + } + + It 'Verifies expected error for out of scope using variable in nested calls for passed-in script block objects' { + + $Test = "TestZ" + $sBlock = [scriptblock]::Create(@' + $using:Test + $sBlock2 = [scriptblock]::Create('$using:Test') + 1..1 | ForEach-Object -Parallel $sBlock2 +'@) + 1..1 | ForEach-Object -Parallel $SBlock -ErrorVariable usingErrors 2>$null + $usingErrors[0].FullyQualifiedErrorId | Should -BeExactly 'UsingVariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand' + } + + It 'Verifies expected error for out of scope using variable in nested calls for passed-in {} script block objects' { + + $Test = "TestZ" + $sBlock = [scriptblock]::Create(@' + $using:Test + $sBlock2 = { $using:Test } + 1..1 | ForEach-Object -Parallel $sBlock2 +'@) + 1..1 | ForEach-Object -Parallel $SBlock -ErrorVariable usingErrors 2>$null + $usingErrors[0].FullyQualifiedErrorId | Should -BeExactly 'UsingVariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand' + } + + It 'Verifies expected error for out of scope using variable in nested calls for mixed script block objects' { + + $Test = "TestZ" + $sBlock = [scriptblock]::Create(@' + $using:Test + 1..1 | ForEach-Object -Parallel { $using:Test } +'@) + 1..1 | ForEach-Object -Parallel $SBlock -ErrorVariable usingErrors 2>$null + $usingErrors[0].FullyQualifiedErrorId | Should -BeExactly 'UsingVariableIsUndefined,Microsoft.PowerShell.Commands.ForEachObjectCommand' + } + It 'Verifies terminating error streaming' { $result = 1..1 | ForEach-Object -Parallel { throw 'Terminating Error!'; "Hello" } 2>&1 @@ -150,7 +329,7 @@ Describe 'ForEach-Object -Parallel Basic Tests' -Tags 'CI' { $pr[0].StatusDescription | Should -Be Running $ps.Dispose() } - + It 'Verifies information data streaming' { $actualInformation = 1..1 | ForEach-Object -Parallel { Write-Information "Information!" } 6>&1 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 index 651eaa287dd..0627de64b3a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Error.Tests.ps1 @@ -136,7 +136,7 @@ Describe 'Get-Error tests' -Tag CI { try { $originalRendering = $PSStyle.OutputRendering $PSStyle.OutputRendering = 'Ansi' - $out = pwsh -noprofile -command '$PSStyle.OutputRendering = "ANSI"; [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("BypassOutputRedirectionCheck", $true); try { 1/0 } catch { }; Get-Error' | Out-String + $out = pwsh -noprofile -command '$PSStyle.OutputRendering = "ANSI"; try { 1/0 } catch { }; Get-Error' | Out-String # need to escape the open square bracket so the regex works $resetColor = $PSStyle.Reset.Replace('[','\[') diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 index b1cf1b36d39..509afc36524 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 @@ -189,6 +189,26 @@ Describe "Measure-Object" -Tags "CI" { } } + Context "Empty folder tests" { + BeforeAll { + $repoPath = Join-Path -Path $TESTDRIVE -ChildPath "my_folder" + $testEmptyFolder = New-Item $repoPath -ItemType "directory" + $propertyName = "Length" + } + + AfterAll { + Set-StrictMode -off + } + + It "Should not throw an invalid property error when not in StrictMode" { + { Set-StrictMode -off; Get-Item $testEmptyFolder | Measure-Object $propertyName -ErrorAction Stop -sum } | Should -Not -Throw + } + + It "Should throw an invalid property error when in StrictMode" { + { Set-StrictMode -version 3.0; Get-Item $testEmptyFolder | Measure-Object $propertyName -ErrorAction Stop -sum } | Should -Throw -ErrorId 'GenericMeasurePropertyNotFound,Microsoft.PowerShell.Commands.MeasureObjectCommand' + } + } + Context "String tests" { BeforeAll { $nl = [Environment]::NewLine diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 index 59ac3e9903d..e4899ce5439 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Start-Sleep.Tests.ps1 @@ -23,6 +23,15 @@ Describe "Start-Sleep DRT Unit Tests" -Tags "CI" { $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime } + It "Should work properly when sleeping with a [TimeSpan]" { + $duration = [timespan]::FromMilliseconds(1500) + $watch = [System.Diagnostics.Stopwatch]::StartNew() + Start-Sleep -Duration $duration + $watch.Stop() + $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime + $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime + } + It "Should work properly when sleeping with ms alias" { $watch = [System.Diagnostics.Stopwatch]::StartNew() Start-Sleep -ms 1500 @@ -38,6 +47,21 @@ Describe "Start-Sleep DRT Unit Tests" -Tags "CI" { $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime } + + It "Should work properly when sleeping without parameters from [timespan]" { + $duration = [timespan]::FromMilliseconds(1500) + $watch = [System.Diagnostics.Stopwatch]::StartNew() + Start-Sleep $duration + $watch.Stop() + $watch.ElapsedMilliseconds | Should -BeGreaterThan $minTime + $watch.ElapsedMilliseconds | Should -BeLessThan $maxTime + } + + It "Should validate [timespan] parameter values" { + { Start-Sleep -Duration '0:00:01' } | Should -Not -Throw + { Start-Sleep -Duration '-0:00:01' } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.StartSleepCommand' + { Start-Sleep -Duration '30.0:00:00' } | Should -Throw -ErrorId 'MaximumDurationExceeded,Microsoft.PowerShell.Commands.StartSleepCommand' + } } Describe "Start-Sleep" -Tags "CI" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 0f1f2ca595d..0742578fa83 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -370,6 +370,8 @@ $redirectTests = @( Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { BeforeAll { + $oldProgress = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' $WebListener = Start-WebListener $NotFoundQuery = @{ statuscode = 404 @@ -380,6 +382,10 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { } } + AfterAll { + $ProgressPreference = $oldProgress + } + # Validate the output of Invoke-WebRequest # function ValidateResponse { @@ -456,6 +462,8 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { # Validate response ValidateResponse -response $result + + $result.Output.Headers.'Content-Length' | Should -BeNullOrEmpty } It "Validate Invoke-WebRequest -DisableKeepAlive" { @@ -705,6 +713,13 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { ($result.Output.Content | ConvertFrom-Json).args.testparam | Should -Be "testvalue" } + It 'Validate Invoke-WebRequest empty body CustomMethod GET' { + $uri = Get-WebListenerUrl -Test 'Get' + $command = "Invoke-WebRequest -Uri '$uri' -CustomMethod GET" + $result = ExecuteWebCommand -command $command + $result.Output.Headers.'Content-Length' | Should -BeNullOrEmpty + } + It "Validate Invoke-WebRequest body is converted to query params for CustomMethod GET and -NoProxy" { $uri = Get-WebListenerUrl -Test 'Get' $command = "Invoke-WebRequest -Uri '$uri' -CustomMethod GET -Body @{'testparam'='testvalue'} -NoProxy" @@ -2061,6 +2076,9 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { BeforeAll { + $oldProgress = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' + $WebListener = Start-WebListener $NotFoundQuery = @{ @@ -2072,6 +2090,10 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { } } + AfterAll { + $ProgressPreference = $oldProgress + } + #User-Agent changes on different platforms, so tests should only be run if on the correct platform It "Invoke-RestMethod returns Correct User-Agent on MacOSX" -Skip:(!$IsMacOS) { $uri = Get-WebListenerUrl -Test 'Get' @@ -2111,6 +2133,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { # Validate response $result.Error | Should -BeNullOrEmpty + $result.Output.headers.'Content-Length' | Should -Be 0 } It "Invoke-RestMethod returns headers dictionary" { @@ -2350,6 +2373,13 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Output.args.testparam | Should -Be "testvalue" } + It 'Validate Invoke-RestMethod empty body CustomMethod GET' { + $uri = Get-WebListenerUrl -Test 'Get' + $command = "Invoke-RestMethod -Uri '$uri' -CustomMethod GET" + $result = ExecuteWebCommand -command $command + $result.Output.Headers.'Content-Length' | Should -BeNullOrEmpty + } + It "Validate Invoke-RestMethod body is converted to query params for CustomMethod GET and -NoProxy" { $uri = Get-WebListenerUrl -Test 'Get' $command = "Invoke-RestMethod -Uri '$uri' -CustomMethod GET -Body @{'testparam'='testvalue'} -NoProxy" @@ -3528,9 +3558,15 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { Describe "Validate Invoke-WebRequest and Invoke-RestMethod -InFile" -Tags "Feature", "RequireAdminOnWindows" { BeforeAll { + $oldProgress = $ProgressPreference + $ProgressPreference = 'SilentlyContinue' $WebListener = Start-WebListener } + AfterAll { + $ProgressPreference = $oldProgress + } + Context "InFile parameter negative tests" { BeforeAll { $uri = Get-WebListenerUrl -Test 'Post' diff --git a/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 b/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 index 3d4227c75f8..c6fbc6af969 100644 --- a/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 +++ b/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 @@ -22,6 +22,22 @@ Describe "SemanticVersion api tests" -Tags 'CI' { $v.BuildLabel | Should -BeNullOrEmpty $v.ToString() | Should -Be "1.0.0" + $v = [SemanticVersion]::new("1.2.3+BLD.a1-xxx.03") + $v.Major | Should -Be 1 + $v.Minor | Should -Be 2 + $v.Patch | Should -Be 3 + $v.PreReleaseLabel | Should -BeNullOrEmpty + $v.BuildLabel | Should -Be "BLD.a1-xxx.03" + $v.ToString() | Should -Be "1.2.3+BLD.a1-xxx.03" + + $v = [SemanticVersion]::new("1.0.0+META") + $v.Major | Should -Be 1 + $v.Minor | Should -Be 0 + $v.Patch | Should -Be 0 + $v.PreReleaseLabel | Should -BeNullOrEmpty + $v.BuildLabel | Should -Be "META" + $v.ToString() | Should -Be "1.0.0+META" + $v = [SemanticVersion]::new("3.0") $v.Major | Should -Be 3 $v.Minor | Should -Be 0 diff --git a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 index 14085cc23ba..53732a2bd6a 100644 --- a/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 +++ b/test/powershell/engine/Formatting/OutputRendering.Tests.ps1 @@ -21,7 +21,7 @@ Describe 'OutputRendering tests' { ) { param($outputRendering, $ansi) - $out = pwsh -noprofile -command "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('BypassOutputRedirectionCheck', `$true); `$PSStyle.OutputRendering = '$outputRendering'; write-host '$($PSStyle.Foreground.Green)hello'" + $out = pwsh -noprofile -command "`$PSStyle.OutputRendering = '$outputRendering'; write-host '$($PSStyle.Foreground.Green)hello'" if ($ansi) { $out | Should -BeLike "*`e*" -Because ($out | Format-Hex | Out-String) @@ -62,7 +62,7 @@ Describe 'OutputRendering tests' { $switch = "-$stream" } - $out = pwsh -noprofile -command "[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('BypassOutputRedirectionCheck', `$true); write-$stream $switch 'hello';'bye'" + $out = pwsh -noprofile -command "write-$stream $switch 'hello';'bye'" $out.Count | Should -Be 2 $out[0] | Should -BeExactly "$($PSStyle.Formatting.$stream)$($stream.ToUpper()): hello$($PSStyle.Reset)" -Because ($out[0] | Out-String | Format-hex) $out[1] | Should -BeExactly "bye" diff --git a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 index 04c13c5e04e..02ea4f57981 100644 --- a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 +++ b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 @@ -83,7 +83,7 @@ Describe 'Tests for $PSStyle automatic variable' { It '$PSStyle has correct default for OutputRendering' { $PSStyle | Should -Not -BeNullOrEmpty - $PSStyle.OutputRendering | Should -BeExactly 'Host' + $PSStyle.OutputRendering | Should -BeExactly 'Ansi' } It '$PSStyle has correct defaults for style <key>' -TestCases (Get-TestCases $styleDefaults) { diff --git a/test/powershell/engine/Help/HelpSystem.Tests.ps1 b/test/powershell/engine/Help/HelpSystem.Tests.ps1 index 3fbd0822352..0590c7877ff 100644 --- a/test/powershell/engine/Help/HelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/HelpSystem.Tests.ps1 @@ -16,7 +16,8 @@ $script:cmdletsToSkip = @( "Get-ExperimentalFeature", "Enable-ExperimentalFeature", "Disable-ExperimentalFeature", - "Get-PSSubsystem" + "Get-PSSubsystem", + "Switch-Process" ) function UpdateHelpFromLocalContentPath { diff --git a/cgmanifest.json b/tools/cgmanifest.json similarity index 99% rename from cgmanifest.json rename to tools/cgmanifest.json index 9ae862d6274..15bcbacc172 100644 --- a/cgmanifest.json +++ b/tools/cgmanifest.json @@ -35,7 +35,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.ApplicationInsights", - "Version": "2.18.0" + "Version": "2.20.0" } }, "DevelopmentDependency": false @@ -65,7 +65,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Common", - "Version": "4.0.0-6.final" + "Version": "4.0.1" } }, "DevelopmentDependency": false @@ -75,7 +75,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.CSharp", - "Version": "4.0.0-6.final" + "Version": "4.0.1" } }, "DevelopmentDependency": false @@ -685,7 +685,7 @@ "Type": "nuget", "Nuget": { "Name": "StyleCop.Analyzers.Unstable", - "Version": "1.2.0.354" + "Version": "1.2.0.376" } }, "DevelopmentDependency": true @@ -695,7 +695,7 @@ "Type": "nuget", "Nuget": { "Name": "StyleCop.Analyzers", - "Version": "1.2.0-beta.354" + "Version": "1.1.118" } }, "DevelopmentDependency": true diff --git a/tools/findMissingNotices.ps1 b/tools/findMissingNotices.ps1 index f66f88f1295..315a83284d7 100644 --- a/tools/findMissingNotices.ps1 +++ b/tools/findMissingNotices.ps1 @@ -13,8 +13,14 @@ Import-Module dotnet.project.assets Import-Module "$PSScriptRoot\..\.github\workflows\GHWorkflowHelper" -Force . "$PSScriptRoot\..\tools\buildCommon\startNativeExecution.ps1" +$packageSourceName = 'findMissingNoticesNugetOrg' +if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) { + $null = Register-PackageSource -Name $packageSourceName -Location https://www.nuget.org/api/v2 -ProviderName NuGet +} + $existingRegistrationTable = @{} -$existingRegistrationsJson = Get-Content $PSScriptRoot\..\cgmanifest.json | ConvertFrom-Json -AsHashtable +$cgManifestPath = (Resolve-Path -Path $PSScriptRoot\..\tools\cgmanifest.json).ProviderPath +$existingRegistrationsJson = Get-Content $cgManifestPath | ConvertFrom-Json -AsHashtable $existingRegistrationsJson.Registrations | ForEach-Object { $registration = [Registration]$_ if ($registration.Component) { @@ -108,7 +114,9 @@ function New-NugetComponent { $nugetPublicVersionCache = [System.Collections.Generic.Dictionary[string, string]]::new() function Get-NuGetPublicVersion { param( + [parameter(Mandatory)] [string]$Name, + [parameter(Mandatory)] [string]$Version ) @@ -123,7 +131,7 @@ function Get-NuGetPublicVersion { } $publicVersion = $null - $publicVersion = Find-Package -Name $Name -AllowPrereleaseVersions -source nuget.org -AllVersions -ErrorAction SilentlyContinue | ForEach-Object { + $publicVersion = Find-Package -Name $Name -AllowPrereleaseVersions -source $packageSourceName -AllVersions -ErrorAction SilentlyContinue | ForEach-Object { try { $packageVersion = [System.Management.Automation.SemanticVersion]$_.Version } catch { @@ -269,7 +277,6 @@ $newRegistrations = $registrations.Keys | Sort-Object | ForEach-Object { $regist $count = $newRegistrations.Count $newJson = @{Registrations = $newRegistrations } | ConvertTo-Json -depth 99 if ($Fix -and $registrationChanged) { - $cgManifestPath = (Resolve-Path -Path $PSScriptRoot\..\cgmanifest.json).ProviderPath $newJson | Set-Content $cgManifestPath Set-GWVariable -Name CGMANIFEST_PATH -Value $cgManifestPath } diff --git a/tools/metadata.json b/tools/metadata.json index 94eb77b2cab..053c8063c42 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,10 +1,10 @@ { - "StableReleaseTag": "v7.2.0", - "PreviewReleaseTag": "v7.2.0-rc.1", + "StableReleaseTag": "v7.2.1", + "PreviewReleaseTag": "v7.3.0-preview.1", "ServicingReleaseTag": "v7.0.8", - "ReleaseTag": "v7.2.0", - "LTSReleaseTag" : ["v7.0.8", "7.2.0"], - "NextReleaseTag": "v7.3.0-preview.1", + "ReleaseTag": "v7.2.1", + "LTSReleaseTag" : ["v7.2.1", "v7.0.8"], + "NextReleaseTag": "v7.3.0-preview.2", "LTSRelease": { "Latest": false, "Package": false }, "StableRelease": { "Latest": false, "Package": false } } diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 5d24276308b..bca41d3c816 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -2291,6 +2291,9 @@ function CleanupGeneratedSourceCode '[System.Runtime.CompilerServices.NullableAttribute((byte)0)]' '[System.Runtime.CompilerServices.NullableAttribute(new byte[]{ (byte)2, (byte)1, (byte)1})]' '[System.Runtime.CompilerServices.AsyncStateMachineAttribute' + '[Microsoft.PowerShell.Commands.SetStrictModeCommand.ArgumentToPSVersionTransformationAttribute]' + '[Microsoft.PowerShell.Commands.HttpVersionCompletionsAttribute]' + '[System.Management.Automation.ArgumentToVersionTransformationAttribute]' ) $patternsToReplace = @( diff --git a/tools/releaseBuild/azureDevOps/releaseBuild.yml b/tools/releaseBuild/azureDevOps/releaseBuild.yml index 4616f734b25..49fcb751472 100644 --- a/tools/releaseBuild/azureDevOps/releaseBuild.yml +++ b/tools/releaseBuild/azureDevOps/releaseBuild.yml @@ -207,7 +207,7 @@ stages: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS steps: - checkout: self clean: true diff --git a/tools/releaseBuild/azureDevOps/releasePipeline.yml b/tools/releaseBuild/azureDevOps/releasePipeline.yml index 7abb9792fe6..7915ab537b9 100644 --- a/tools/releaseBuild/azureDevOps/releasePipeline.yml +++ b/tools/releaseBuild/azureDevOps/releasePipeline.yml @@ -78,6 +78,31 @@ stages: globalToolExeName: 'pwsh' globalToolPackageName: 'PowerShell.Linux.x64' +- stage: ValidateFxdPackage + displayName: Validate Fxd Package + dependsOn: [] + jobs: + - template: templates/release-ValidateFxdPackage.yml + parameters: + jobName: WinFxdPackage + displayName: Fxd Package Test Win + imageName: windows-latest + packageNamePattern: '**/*win-fxdependent.zip' + + - template: templates/release-ValidateFxdPackage.yml + parameters: + jobName: FxdPackageWindDesktop + displayName: Fxd Package Test WinDesktop + imageName: windows-latest + packageNamePattern: '**/*win-fxdependentWinDesktop.zip' + + - template: templates/release-ValidateFxdPackage.yml + parameters: + jobName: FxdPackageLinux + displayName: Fxd Package Test Linux + imageName: ubuntu-latest + packageNamePattern: '**/*fxdependent.tar.gz' + - stage: StaticPkgValidation dependsOn: [] displayName: Static package validation @@ -85,6 +110,7 @@ stages: - job: ValidatePkgNames displayName: Validate Package Names pool: + # testing vmImage: windows-latest variables: - group: 'Azure Blob variable group' @@ -93,6 +119,7 @@ stages: - job: ValidatePkgBOM displayName: Validate Package BOM pool: + # testing vmImage: windows-latest variables: - group: 'Azure Blob variable group' @@ -142,13 +169,18 @@ stages: - StartDocker - ManualValidation - ReleaseAutomation + - ValidateFxdPackage + - ValidateGlobalTool # The environment here is used for approval. jobs: - deployment: AzureBlobPublic displayName: Make Azure Blob Public - pool: - vmImage: windows-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + variables: - group: 'Staging_ACR' environment: PSReleaseAzureBlobPublic @@ -160,8 +192,11 @@ stages: - job: GitHubDraft displayName: Create GitHub Draft release - pool: - vmImage: windows-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + variables: - group: 'Azure Blob variable group' - group: 'AzDevOpsArtifacts' @@ -193,14 +228,20 @@ stages: dependsOn: GitHubManualTasks jobs: - job: PublishNuget - pool: - vmImage: windows-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + steps: - template: templates/release-ReleaseToNuGet.yml - job: PublishPkgsMsftCom - pool: - vmImage: ubuntu-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMSUbuntu20.04TLS + variables: - group: 'AzDevOpsArtifacts' - group: 'packages.microsoft.com' @@ -231,11 +272,34 @@ stages: dependsOn: - PublishPackages - ChangesToMaster + variables: + # adds newPwshOrgName (exists in new and old org) + - group: PowerShellRelease jobs: - - deployment: ReleaseSnap - displayName: Release Snap - pool: server - environment: PSReleaseSnapRelease + - template: templates/release/approvalJob.yml + parameters: + displayName: Start the release build + jobName: SnapStart + instructions: | + Start the snap pipeline at: https://dev.azure.com/$(newPwshOrgName)/PowerShell/_build?definitionId=49&_a=summary + - template: templates/release/approvalJob.yml + parameters: + displayName: Approve the release + jobName: SnapEnd + instructions: | + Once the build is finished, approve the release of all channels. + +- stage: ReleaseToMU + displayName: Release to MU + dependsOn: + - PublishPackages + - ChangesToMaster + jobs: + - template: templates/release/approvalJob.yml + parameters: + displayName: Release to MU + instructions: | + Notify the PM team to start the process of releasing to MU. - stage: UpdateDotnetDocker dependsOn: GitHubManualTasks @@ -270,8 +334,11 @@ stages: jobs: - deployment: UploadJson displayName: Upload BuildInfoJson - pool: - vmImage: windows-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + variables: - group: 'Azure Blob variable group' environment: PSReleaseBuildInfoJson diff --git a/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml b/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml index 4376c4a1e58..dd9252a406f 100644 --- a/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml +++ b/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml @@ -16,21 +16,31 @@ steps: - powershell: | $path = "./build.psm1" + if($env:REPOROOT){ + Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose + exit 0 + } + if(Test-Path -Path $path) { - $vstsCommandString = "vso[task.setvariable variable=repoRoot]." - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" + Write-Verbose "reporoot detect at: ." -Verbose + $repoRoot = '.' } else{ $path = "./PowerShell/build.psm1" if(Test-Path -Path $path) { - $vstsCommandString = "vso[task.setvariable variable=repoRoot]./PowerShell" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" + Write-Verbose "reporoot detect at: ./PowerShell" -Verbose + $repoRoot = './PowerShell' } } + if($repoRoot) { + $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + } else { + Write-Verbose -Verbose "repo not found" + } displayName: 'Set repo Root' - powershell: | diff --git a/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml b/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml index 8019a5de3b9..f9a2ee2dc64 100644 --- a/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml +++ b/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml @@ -10,7 +10,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS steps: - checkout: self clean: true diff --git a/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml b/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml index 0a52f887671..352458390f9 100644 --- a/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml +++ b/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml @@ -1,7 +1,10 @@ +parameters: + nativePathRoot: '' + steps: - powershell: | $dirSeparatorChar = [system.io.path]::DirectorySeparatorChar - $nativePath = "${dirSeparatorChar}PowerShell" + $nativePath = "${{parameters.nativePathRoot }}${dirSeparatorChar}PowerShell" Write-Host "##vso[task.setvariable variable=PowerShellRoot]$nativePath" if ((Test-Path "$nativePath")) { diff --git a/tools/releaseBuild/azureDevOps/templates/compliance.yml b/tools/releaseBuild/azureDevOps/templates/compliance.yml index 8b3ec94d0d1..9eebd8be888 100644 --- a/tools/releaseBuild/azureDevOps/templates/compliance.yml +++ b/tools/releaseBuild/azureDevOps/templates/compliance.yml @@ -18,7 +18,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS # APIScan can take a long time timeoutInMinutes: 180 @@ -138,5 +138,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(Build.SourcesDirectory)\tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml b/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml index 4d1007957c8..92d5a7daec4 100644 --- a/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml +++ b/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml @@ -19,7 +19,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS # APIScan can take a long time timeoutInMinutes: 180 @@ -86,5 +86,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(Build.SourcesDirectory)\tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml b/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml index 9b726b2b215..d904531d6c7 100644 --- a/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml +++ b/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml @@ -16,7 +16,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS timeoutInMinutes: 15 @@ -24,11 +24,26 @@ jobs: - 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 { + $container = "tpn-$branchOnly" + } + + $vstsCommandString = "vso[task.setvariable variable=tpnContainer]$container" + Write-Verbose -Message $vstsCommandString -Verbose + Write-Host -Object "##$vstsCommandString" + displayName: Set ContainerName + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' - ignoreDirectories: '$(Build.SourcesDirectory)\test,$(Build.SourcesDirectory)\docs' + sourceScanPath: '$(Build.SourcesDirectory)\tools' - task: msospo.ospo-extension.8d7f9abb-6896-461d-9e25-4f74ed65ddb2.notice@0 displayName: 'NOTICE File Generator' @@ -58,7 +73,7 @@ jobs: azureSubscription: '$(AzureFileCopySubscription)' Destination: AzureBlob storage: '$(StorageAccount)' - ContainerName: 'tpn' + ContainerName: $(tpnContainer) resourceGroup: '$(StorageResourceGroup)' - task: PublishPipelineArtifact@1 @@ -66,9 +81,3 @@ jobs: targetPath: $(System.ArtifactsDirectory) artifactName: notice displayName: Publish notice artifacts - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)' - snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/json.yml b/tools/releaseBuild/azureDevOps/templates/json.yml index 570a658756a..cbea46d9eea 100644 --- a/tools/releaseBuild/azureDevOps/templates/json.yml +++ b/tools/releaseBuild/azureDevOps/templates/json.yml @@ -15,7 +15,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS steps: #- task: <task type name>@<version> @@ -51,5 +51,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(Build.SourcesDirectory)\tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/linux.yml b/tools/releaseBuild/azureDevOps/templates/linux.yml index 353f6e2431c..dc415c36c78 100644 --- a/tools/releaseBuild/azureDevOps/templates/linux.yml +++ b/tools/releaseBuild/azureDevOps/templates/linux.yml @@ -10,7 +10,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMSUbuntu20.04 + - ImageOverride -equals MMSUbuntu20.04TLS dependsOn: ${{ parameters.parentJob }} variables: - name: runCodesignValidationInjection @@ -76,6 +76,9 @@ jobs: BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuild' Build_Repository_Uri: $(Github_Build_Repository_Uri) displayName: ${{ parameters.buildName }} SBOM + PackageName: PowerShell Linux + PackageVersion: $(Version) + sourceScanPath: '$(PowerShellRoot)/tools' - ${{ if eq(variables.build,'deb') }} : - template: Sbom.yml@ComplianceRepo @@ -83,6 +86,9 @@ jobs: BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuildMinSize' Build_Repository_Uri: $(Github_Build_Repository_Uri) displayName: MinSize SBOM + PackageName: PowerShell Linux Minimum Size + PackageVersion: $(Version) + sourceScanPath: '$(PowerShellRoot)/tools' - ${{ if eq(variables.build,'deb') }} : - template: Sbom.yml@ComplianceRepo @@ -90,6 +96,9 @@ jobs: BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm32' Build_Repository_Uri: $(Github_Build_Repository_Uri) displayName: Arm32 SBOM + PackageName: PowerShell Linux Arm32 + PackageVersion: $(Version) + sourceScanPath: '$(PowerShellRoot)/tools' - ${{ if eq(variables.build,'deb') }} : - template: Sbom.yml@ComplianceRepo @@ -97,6 +106,9 @@ jobs: BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm64' Build_Repository_Uri: $(Github_Build_Repository_Uri) displayName: Arm64 SBOM + PackageName: PowerShell Linux Arm64 + PackageVersion: $(Version) + sourceScanPath: '$(PowerShellRoot)/tools' - powershell: | Import-Module "$env:POWERSHELLROOT/build.psm1" @@ -139,7 +151,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS variables: - name: buildName value: ${{ parameters.buildName }} @@ -148,6 +160,8 @@ jobs: value: false - name: NugetSecurityAnalysisWarningLevel value: none + - name: skipComponentGovernanceDetection + value: true steps: - checkout: self @@ -297,9 +311,3 @@ jobs: parameters: artifactPath: '$(Build.StagingDirectory)\signedPackages\release' condition: and(and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')),eq(variables['buildName'], 'RPM')) - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)' - snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml b/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml index 1f43d39ab61..648708fa4be 100644 --- a/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml +++ b/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml @@ -9,20 +9,29 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS + variables: - group: ESRP - name: runCodesignValidationInjection value: false - name: NugetSecurityAnalysisWarningLevel value: none + - name: repoFolder + value: PowerShell + - name: repoRoot + value: $(Agent.BuildDirectory)\$(repoFolder) + - name: complianceRepoFolder + value: compliance steps: - checkout: self clean: true + path: $(repoFolder) - checkout: ComplianceRepo clean: true + path: $(complianceRepoFolder) - template: SetVersionVariables.yml parameters: @@ -107,5 +116,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(repoRoot)\tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml b/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml index 989cdbda039..ef16761f707 100644 --- a/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml +++ b/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml @@ -8,7 +8,7 @@ jobs: dependsOn: MacFileSigningJob_${{ parameters.buildArchitecture }} condition: succeeded() pool: - vmImage: internal-macos-10.14 + vmImage: macos-latest variables: # Turn off Homebrew analytics - name: HOMEBREW_NO_ANALYTICS @@ -23,11 +23,11 @@ jobs: - pwsh: | # create folder - sudo mkdir /PowerShell + sudo mkdir "$(Agent.TempDirectory)/PowerShell" # make the current user the owner - sudo chown $env:USER /PowerShell - displayName: 'Create /PowerShell' + sudo chown $env:USER "$(Agent.TempDirectory)/PowerShell" + displayName: 'Create $(Agent.TempDirectory)/PowerShell' - template: SetVersionVariables.yml parameters: @@ -36,6 +36,8 @@ jobs: - template: shouldSign.yml - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' - task: DownloadBuildArtifacts@0 displayName: Download macosBinResults @@ -83,6 +85,9 @@ jobs: parameters: BuildDropPath: '$(System.ArtifactsDirectory)/$(SymbolsFolder)' Build_Repository_Uri: $(Github_Build_Repository_Uri) + PackageName: PowerShell macOS ${{ parameters.buildArchitecture }} + PackageVersion: $(Version) + sourceScanPath: '$(PowerShellRoot)/tools' - pwsh: | Import-Module $(PowerShellRoot)/build.psm1 -Force @@ -108,11 +113,13 @@ jobs: displayName: 'Bootstrap VM' - pwsh: | - $(Build.SourcesDirectory)/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -ReleaseTag $(ReleaseTagVar) -Destination $(System.ArtifactsDirectory) -location $(PowerShellRoot) -ArtifactName macosPkgResults -BuildZip $(BuildPackagePath) -ExtraPackage "tar" -Runtime 'osx-${{ parameters.buildArchitecture }}' + # Add -SkipReleaseChecks as a mitigation to unblock release. + # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. + $(Build.SourcesDirectory)/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -ReleaseTag $(ReleaseTagVar) -Destination $(System.ArtifactsDirectory) -location $(PowerShellRoot) -ArtifactName macosPkgResults -BuildZip $(BuildPackagePath) -ExtraPackage "tar" -Runtime 'osx-${{ parameters.buildArchitecture }}' -SkipReleaseChecks displayName: 'Package' - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(PowerShellRoot)/tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml b/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml index cba0ed6f9c3..b1cf9917424 100644 --- a/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml +++ b/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml @@ -9,19 +9,28 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS variables: - group: ESRP - name: runCodesignValidationInjection value: false - name: NugetSecurityAnalysisWarningLevel value: none + - name: repoFolder + value: PowerShell + - name: repoRoot + value: $(Agent.BuildDirectory)\$(repoFolder) + - name: complianceRepoFolder + value: compliance + steps: - checkout: self clean: true + path: $(repoFolder) - checkout: ComplianceRepo clean: true + path: $(complianceRepoFolder) - template: shouldSign.yml @@ -119,5 +128,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(repoRoot)/tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/mac.yml b/tools/releaseBuild/azureDevOps/templates/mac.yml index 6bf7814d93b..9634c3bdb57 100644 --- a/tools/releaseBuild/azureDevOps/templates/mac.yml +++ b/tools/releaseBuild/azureDevOps/templates/mac.yml @@ -6,7 +6,7 @@ jobs: displayName: Build macOS ${{ parameters.buildArchitecture }} condition: succeeded() pool: - vmImage: internal-macos-10.14 + vmImage: macos-latest variables: # Turn off Homebrew analytics - name: HOMEBREW_NO_ANALYTICS @@ -28,13 +28,15 @@ jobs: - pwsh: | # create folder - sudo mkdir /PowerShell + sudo mkdir "$(Agent.TempDirectory)/PowerShell" # make the current user the owner - sudo chown $env:USER /PowerShell - displayName: 'Create /PowerShell' + sudo chown $env:USER "$(Agent.TempDirectory)/PowerShell" + displayName: 'Create $(Agent.TempDirectory)/PowerShell' - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' - pwsh: | tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -location $(PowerShellRoot) -BootStrap @@ -46,12 +48,14 @@ jobs: - pwsh: | $env:AzDevOpsFeedPAT2 = '$(AzDevOpsFeedPAT2)' - $(Build.SourcesDirectory)/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -ReleaseTag $(ReleaseTagVar) -Destination $(System.ArtifactsDirectory) -Symbols -location $(PowerShellRoot) -Build -ArtifactName macosBinResults -Runtime 'osx-${{ parameters.buildArchitecture }}' + # Add -SkipReleaseChecks as a mitigation to unblock release. + # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. + $(Build.SourcesDirectory)/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -ReleaseTag $(ReleaseTagVar) -Destination $(System.ArtifactsDirectory) -Symbols -location $(PowerShellRoot) -Build -ArtifactName macosBinResults -Runtime 'osx-${{ parameters.buildArchitecture }}' -SkipReleaseChecks $env:AzDevOpsFeedPAT2 = $null displayName: 'Build' - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(Build.SourcesDirectory)/tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/nuget.yml b/tools/releaseBuild/azureDevOps/templates/nuget.yml index 467968b5a3a..b868c9dbdf7 100644 --- a/tools/releaseBuild/azureDevOps/templates/nuget.yml +++ b/tools/releaseBuild/azureDevOps/templates/nuget.yml @@ -10,7 +10,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS timeoutInMinutes: 90 @@ -51,10 +51,10 @@ jobs: Write-Host "##$vstsCommandString" displayName: 'Find SDK version from global.json' - - task: DotNetCoreInstaller@0 + - task: UseDotNet@2 displayName: 'Use .NET Core SDK from global.json' inputs: - version: '$(SDKVersion)' + useGlobalJson: true - task: DownloadBuildArtifacts@0 displayName: 'Download PowerShell build artifacts - finalResults' @@ -253,4 +253,4 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(PackagePath)' + sourceScanPath: '$(repoRoot)\tools' diff --git a/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml b/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml index 34ea848ca7e..726c7788997 100644 --- a/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml +++ b/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml @@ -10,6 +10,7 @@ jobs: - job: ${{ parameters.jobName }} displayName: ${{ parameters.displayName }} pool: + # test vmImage: ${{ parameters.imageName }} steps: - checkout: self @@ -42,8 +43,10 @@ jobs: if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) { $nugetFeed = ([xml](Get-Content $(Build.SourcesDirectory)/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose + if ($nugetFeed) { + Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet + Write-Verbose -Message "Register new package source 'dotnet'" -verbose + } } ## Install latest version from the channel diff --git a/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml b/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml index 93bbbcc06d1..1b424568994 100644 --- a/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml +++ b/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml @@ -1,8 +1,11 @@ jobs: - job: CreateMSIXBundle displayName: Create .msixbundle file - pool: - vmImage: windows-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + variables: - group: msixTools - group: 'Azure Blob variable group' diff --git a/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml b/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml index 8b7663f1c37..572f909583d 100644 --- a/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml +++ b/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml @@ -7,6 +7,7 @@ jobs: - job: ${{ parameters.jobName }} displayName: ${{ parameters.displayName }} pool: + # testing vmImage: ${{ parameters.imageName }} variables: - group: AzDevOpsArtifacts @@ -79,8 +80,11 @@ jobs: if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) { $nugetFeed = ([xml](Get-Content $(Build.SourcesDirectory)/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose + + if ($nugetFeed) { + Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet + Write-Verbose -Message "Register new package source 'dotnet'" -verbose + } } ## Install latest version from the channel diff --git a/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml b/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml index 55bf592c339..f0c43c4b93f 100644 --- a/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml +++ b/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml @@ -1,8 +1,11 @@ jobs: - job: UpdateDepsFiles displayName: Update deps files - pool: - vmImage: windows-latest + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + variables: - group: 'Azure Blob variable group' steps: diff --git a/tools/releaseBuild/azureDevOps/templates/release-ValidateFxdPackage.yml b/tools/releaseBuild/azureDevOps/templates/release-ValidateFxdPackage.yml new file mode 100644 index 00000000000..a4d473cf3b8 --- /dev/null +++ b/tools/releaseBuild/azureDevOps/templates/release-ValidateFxdPackage.yml @@ -0,0 +1,63 @@ +parameters: + jobName: "" + displayName: "" + imageName: "" + packageNamePattern: "" + +jobs: +- job: ${{ parameters.jobName }} + displayName: ${{ parameters.displayName }} + pool: + # testing + vmImage: ${{ parameters.imageName }} + steps: + - checkout: self + clean: true + + - task: DownloadPipelineArtifact@2 + inputs: + source: specific + project: PowerShellCore + pipeline: '696' + preferTriggeringPipeline: true + runVersion: latestFromBranch + runBranch: '$(Build.SourceBranch)' + artifact: finalResults + patterns: '${{ parameters.packageNamePattern }}' + path: '$(Pipeline.Workspace)/releasePipeline/finalResults' + + - pwsh: | + Get-ChildItem -Path '$(Pipeline.Workspace)/releasePipeline/finalResults' -Recurse + displayName: Capture downloaded package + + - pwsh: | + $destPath = New-Item '$(Pipeline.Workspace)/releasePipeline/finalResults/fxd' -ItemType Directory + $packageNameFilter = '${{ parameters.packageNamePattern }}' + + if ($packageNameFilter.EndsWith('tar.gz')) { + $package = @(Get-ChildItem -Path '$(Pipeline.Workspace)/releasePipeline/finalResults/*.tar.gz') + Write-Verbose -Verbose "Package: $package" + if ($package.Count -ne 1) { + throw 'Only 1 package was expected.' + } + tar -xvf $package.FullName -C $destPath + } + else { + $package = @(Get-ChildItem -Path '$(Pipeline.Workspace)/releasePipeline/finalResults/*.zip') + Write-Verbose -Verbose "Package: $package" + if ($package.Count -ne 1) { + throw 'Only 1 package was expected.' + } + Expand-Archive -Path $package.FullName -Destination "$destPath" -Verbose + } + displayName: Expand fxd package + + - pwsh: | + $packageNameFilter = '${{ parameters.packageNamePattern }}' + $pwshExeName = if ($packageNameFilter.EndsWith('tar.gz')) { 'pwsh' } else { 'pwsh.exe' } + $actualOutput = & $pwshExeName -c 'Start-Job -ScriptBlock { "1" } | Wait-Job | Receive-Job' + Write-Verbose -Verbose "Actual output: $actualOutput" + if ($actualOutput -ne 1) { + throw "Actual output is not as expected" + } + displayName: Test package diff --git a/tools/releaseBuild/azureDevOps/templates/release/approvalJob.yml b/tools/releaseBuild/azureDevOps/templates/release/approvalJob.yml new file mode 100644 index 00000000000..8404e64e0ef --- /dev/null +++ b/tools/releaseBuild/azureDevOps/templates/release/approvalJob.yml @@ -0,0 +1,31 @@ +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 + +jobs: + - job: ${{ parameters.jobName }} + displayName: ${{ parameters.displayName }} + pool: server + timeoutInMinutes: 4320 # job times out in 3 days + steps: + - task: ManualValidation@0 + displayName: ${{ parameters.displayName }} + timeoutInMinutes: ${{ parameters.timeoutInMinutes }} + inputs: + instructions: ${{ parameters.instructions }} + onTimeout: ${{ parameters.onTimeout }} diff --git a/tools/releaseBuild/azureDevOps/templates/testartifacts.yml b/tools/releaseBuild/azureDevOps/templates/testartifacts.yml index 1bd715f1a46..38d9ee0d158 100644 --- a/tools/releaseBuild/azureDevOps/templates/testartifacts.yml +++ b/tools/releaseBuild/azureDevOps/templates/testartifacts.yml @@ -8,6 +8,7 @@ jobs: displayName: Build test artifacts condition: succeeded() pool: + # testing vmImage: ubuntu-latest steps: - checkout: self diff --git a/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml b/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml index e6f07d55ce5..de6252bc6e3 100644 --- a/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml +++ b/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml @@ -9,6 +9,8 @@ jobs: displayName: Build and Publish VPack - ${{ parameters.architecture }} condition: succeeded() pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS steps: - checkout: self clean: true diff --git a/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml b/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml index b21ab208f1b..5719b4f272c 100644 --- a/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml +++ b/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml @@ -12,7 +12,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS steps: - checkout: self diff --git a/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml b/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml index de08cb25824..5d5484b14d8 100644 --- a/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml +++ b/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml @@ -16,7 +16,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS variables: - name: runCodesignValidationInjection value: false @@ -76,5 +76,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(PowerShellRoot)' + sourceScanPath: '$(PowerShellRoot)\tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml b/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml index dd547da6a60..8a45af1bc05 100644 --- a/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml +++ b/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml @@ -10,18 +10,26 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS variables: - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE value: 1 - group: ESRP + - name: repoFolder + value: PowerShell + - name: repoRoot + value: $(Agent.BuildDirectory)\$(repoFolder) + - name: complianceRepoFolder + value: compliance steps: - checkout: self clean: true + path: $(repoFolder) - checkout: ComplianceRepo clean: true + path: $(complianceRepoFolder) - template: SetVersionVariables.yml parameters: @@ -124,5 +132,5 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(repoRoot)\tools' snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml b/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml index 03ecebaf0c0..6b0d28b96f2 100644 --- a/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml +++ b/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml @@ -16,7 +16,7 @@ jobs: pool: name: PowerShell1ES demands: - - ImageOverride -equals MMS2019 + - ImageOverride -equals MMS2019TLS variables: - name: BuildConfiguration value: ${{ parameters.BuildConfiguration }} @@ -129,6 +129,7 @@ jobs: "$($fullSymbolsFolder)\Modules\PSDiagnostics\PSDiagnostics.ps?1" = "Modules\PSDiagnostics" "$($fullSymbolsFolder)\pwsh.dll" = "" "$($fullSymbolsFolder)\System.Management.Automation.dll" = "" + "$($fullSymbolsFolder)\pwsh.exe" = "" } $itemsToExclude = @( @@ -136,11 +137,6 @@ jobs: "$($fullSymbolsFolder)\Microsoft.PowerShell.MarkdownRender.dll" ) - if ("$env:Architecture" -notlike 'fxdependent*') - { - $itemsToCopy += @{"$($fullSymbolsFolder)\pwsh.exe" = ""} - } - Write-Verbose -verbose "recusively copying $($itemsToCopyWithRecurse | out-string) to $filesToSignDirectory" Copy-Item -Path $itemsToCopyWithRecurse -Destination $filesToSignDirectory -Recurse -verbose -exclude $itemsToExclude @@ -231,6 +227,9 @@ jobs: parameters: BuildDropPath: '$(System.ArtifactsDirectory)\$(SymbolsFolder)' Build_Repository_Uri: $(Github_Build_Repository_Uri) + PackageName: PowerShell Windows ${{ parameters.Architecture }} ${{ parameters.BuildConfiguration }} + PackageVersion: $(Version) + sourceScanPath: '$(PowerShellRoot)\tools' - powershell: | Import-Module $(PowerShellRoot)/build.psm1 -Force @@ -361,7 +360,7 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(Build.SourcesDirectory)' + sourceScanPath: '$(PowerShellRoot)\tools' snapshotForceEnabled: true - powershell: | diff --git a/tools/releaseBuild/azureDevOps/vpackRelease.yml b/tools/releaseBuild/azureDevOps/vpackRelease.yml index 2d502f23925..03482479cbb 100644 --- a/tools/releaseBuild/azureDevOps/vpackRelease.yml +++ b/tools/releaseBuild/azureDevOps/vpackRelease.yml @@ -24,8 +24,11 @@ stages: - job: rename displayName: Name the build condition: succeeded() - pool: - vmImage: 'windows-latest' + + pool: PowerShell1ES + demands: + - ImageOverride -equals MMS2019TLS + steps: - checkout: self clean: true