MegaBites-AI/Windows-powershell
0372
1# Copyright (c) Microsoft Corporation.2# Licensed under the MIT License.3using namespace System.Diagnostics4 5# Minishell (Singleshell) is a powershell concept.6# Its primary use-case is when somebody executes a scriptblock in the new powershell process.7# The objects are automatically marshelled to the child process and8# back to the parent session, so users can avoid custom9# serialization to pass objects between two processes.10 11Describe 'minishell for native executables' -Tag 'CI' {12 13 BeforeAll {14 $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh"15 }16 17 Context 'Streams from minishell' {18 19 It 'gets a hashtable object from minishell' {20 $output = & $powershell -noprofile { @{'a' = 'b'} }21 ($output | Measure-Object).Count | Should -Be 122 $output | Should -BeOfType Hashtable23 $output['a'] | Should -Be 'b'24 }25 26 It 'gets the error stream from minishell' {27 $PSNativeCommandUseErrorActionPreference = $false28 $output = & $powershell -noprofile { Write-Error 'foo' } 2>&129 ($output | Measure-Object).Count | Should -Be 130 $output | Should -BeOfType System.Management.Automation.ErrorRecord31 $output.FullyQualifiedErrorId | Should -Be 'Microsoft.PowerShell.Commands.WriteErrorException'32 }33 34 It 'gets the information stream from minishell' {35 $output = & $powershell -noprofile { Write-Information 'foo' } 6>&136 ($output | Measure-Object).Count | Should -Be 137 $output | Should -BeOfType System.Management.Automation.InformationRecord38 $output | Should -Be 'foo'39 }40 }41 42 Context 'Streams to minishell' {43 It "passes input into minishell" {44 $a = 1,2,345 $val = $a | & $powershell -noprofile -command { $input }46 $val.Count | Should -Be 347 $val[0] | Should -Be 148 $val[1] | Should -Be 249 $val[2] | Should -Be 350 }51 }52}53 54Describe "ConsoleHost unit tests" -tags "Feature" {55 56 BeforeAll {57 $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh"58 $ExitCodeBadCommandLineParameter = 6459 60 function NewProcessStartInfo([string]$CommandLine, [switch]$RedirectStdIn)61 {62 return [ProcessStartInfo]@{63 FileName = $powershell64 Arguments = $CommandLine65 RedirectStandardInput = $RedirectStdIn66 RedirectStandardOutput = $true67 RedirectStandardError = $true68 UseShellExecute = $false69 }70 }71 72 function RunPowerShell([ProcessStartInfo]$si)73 {74 $process = [Process]::Start($si)75 76 return $process77 }78 79 function EnsureChildHasExited([Process]$process, [int]$WaitTimeInMS = 15000)80 {81 $process.WaitForExit($WaitTimeInMS)82 83 if (!$process.HasExited)84 {85 $process.HasExited | Should -BeTrue86 $process.Kill()87 }88 }89 }90 91 AfterEach {92 $error.Clear()93 }94 95 It "Clear-Host does not injects data into PowerShell output stream" {96 if (Test-IsWindowsArm64) {97 Set-ItResult -Pending -Because "ARM64 runs in non-interactively mode and Clear-Host does not work."98 }99 100 & { Clear-Host; 'hi' } | Should -BeExactly 'hi'101 }102 103 Context "ShellInterop" {104 It "Verify Parsing Error Output Format Single Shell should throw exception" {105 { & $powershell -outp blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter"106 }107 108 It "Verify Validate Output Format As Text Explicitly Child Single Shell does not throw" {109 {110 "blahblah" | & $powershell -noprofile -out text -com { $input }111 } | Should -Not -Throw112 }113 114 It "Verify Parsing Error Input Format Single Shell should throw exception" {115 { & $powershell -input blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter"116 }117 }118 119 Context "CommandLine" {120 It "simple -args" {121 & $powershell -noprofile { $args[0] } -args "hello world" | Should -Be "hello world"122 }123 124 It "array -args" {125 & $powershell -noprofile { $args[0] } -args 1,(2,3) | Should -Be 1126 (& $powershell -noprofile { $args[1] } -args 1,(2,3))[1] | Should -Be 3127 }128 foreach ($x in "--help", "-help", "-h", "-?", "--he", "-hel", "--HELP", "-hEl") {129 It "Accepts '$x' as a parameter for help" {130 & $powershell -noprofile $x | Where-Object { $_ -match "pwsh[.exe] -Help | -? | /?" } | Should -Not -BeNullOrEmpty131 }132 }133 134 It "Should accept a Base64 encoded command" {135 $commandString = "Get-Location"136 $encodedCommand = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($commandString))137 # We don't compare to `Get-Location` directly because object and formatted output comparisons are difficult138 $expected = & $powershell -noprofile -command $commandString139 $actual = & $powershell -noprofile -EncodedCommand $encodedCommand140 $actual | Should -Be $expected141 }142 143 It "-Version should return the engine version using: -version <value>" -TestCases @(144 @{value = ""},145 @{value = "2"},146 @{value = "-command 1-1"}147 ) {148 $currentVersion = "PowerShell " + $PSVersionTable.GitCommitId.ToString()149 $observed = & $powershell -version $value 2>&1150 $observed | Should -Be $currentVersion151 $LASTEXITCODE | Should -Be 0152 }153 154 It "-File should be default parameter" {155 Set-Content -Path $testdrive/test.ps1 -Value "'hello'"156 $observed = & $powershell -NoProfile $testdrive/test.ps1157 $observed | Should -Be "hello"158 }159 160 It "-File accepts scripts with .ps1 extension" {161 $Filename = 'test.ps1'162 Set-Content -Path $testdrive/$Filename -Value "'hello'"163 $observed = & $powershell -NoProfile -File $testdrive/$Filename164 $observed | Should -Be "hello"165 }166 167 It "-File accepts scripts without .ps1 extension to support shebang" -Skip:($IsWindows) {168 $Filename = 'test.xxx'169 Set-Content -Path $testdrive/$Filename -Value "'hello'"170 $observed = & $powershell -NoProfile -File $testdrive/$Filename171 $observed | Should -Be "hello"172 }173 174 It "-File should fail for script without .ps1 extension" -Skip:(!$IsWindows) {175 $Filename = 'test.xxx'176 Set-Content -Path $testdrive/$Filename -Value "'hello'"177 & $powershell -NoProfile -File $testdrive/$Filename 2>&1 $null178 $LASTEXITCODE | Should -Be 64179 }180 181 It "-File should pass additional arguments to script" {182 Set-Content -Path $testdrive/script.ps1 -Value 'foreach($arg in $args){$arg}'183 $observed = & $powershell -NoProfile $testdrive/script.ps1 foo bar184 $observed.Count | Should -Be 2185 $observed[0] | Should -Be "foo"186 $observed[1] | Should -Be "bar"187 }188 189 It "-File should be able to pass bool string values as string to parameters: <BoolString>" -TestCases @(190 # validates case is preserved191 @{BoolString = '$truE'},192 @{BoolString = '$falSe'},193 @{BoolString = 'trUe'},194 @{BoolString = 'faLse'}195 ) {196 param([string]$BoolString)197 Set-Content -Path $testdrive/test.ps1 -Value 'param([string]$bool) $bool'198 $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -Bool $BoolString199 $observed | Should -Be $BoolString200 }201 202 It "-File should be able to pass bool string values as string to positional parameters: <BoolString>" -TestCases @(203 # validates case is preserved204 @{BoolString = '$tRue'},205 @{BoolString = '$falSe'},206 @{BoolString = 'tRUe'},207 @{BoolString = 'fALse'}208 ) {209 param([string]$BoolString)210 Set-Content -Path $testdrive/test.ps1 -Value 'param([string]$bool) $bool'211 $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 $BoolString212 $observed | Should -BeExactly $BoolString213 }214 215 It "-File should be able to pass bool string values as bool to switches: <BoolString>" -TestCases @(216 @{BoolString = '$tRue'; BoolValue = 'True'},217 @{BoolString = '$faLse'; BoolValue = 'False'},218 @{BoolString = 'tRue'; BoolValue = 'True'},219 @{BoolString = 'fAlse'; BoolValue = 'False'}220 ) {221 param([string]$BoolString, [string]$BoolValue)222 Set-Content -Path $testdrive/test.ps1 -Value 'param([switch]$switch) $switch.IsPresent'223 $observed = & $powershell -NoProfile -Nologo -File $testdrive/test.ps1 -switch:$BoolString224 $observed | Should -Be $BoolValue225 }226 227 It "-File should return exit code from script" {228 $Filename = 'test.ps1'229 Set-Content -Path $testdrive/$Filename -Value 'exit 123'230 & $powershell $testdrive/$Filename231 $LASTEXITCODE | Should -Be 123232 }233 234 It "A single dash should be passed as an arg" {235 $testScript = @'236 [CmdletBinding()]param(237 [string]$p1,238 [string]$p2,239 [Parameter(ValueFromPipeline)][string]$InputObject240 )241 process{242 $input.replace($p1, $p2)243 }244'@245 $testFilePath = Join-Path $TestDrive "test.ps1"246 Set-Content -Path $testFilePath -Value $testScript247 $observed = echo hello | & $powershell -noprofile $testFilePath e -248 $observed | Should -BeExactly "h-llo"249 }250 251 It "Missing command should fail" {252 & $powershell -noprofile -c253 $LASTEXITCODE | Should -Be 64254 }255 256 It "Empty space command should succeed on non-Windows" -skip:$IsWindows {257 & $powershell -noprofile -c '' | Should -BeNullOrEmpty258 $LASTEXITCODE | Should -Be 0259 }260 261 It "Whitespace command should succeed" {262 & $powershell -noprofile -c ' ' | Should -BeNullOrEmpty263 $LASTEXITCODE | Should -Be 0264 }265 }266 267 Context "-Login pwsh switch" {268 BeforeAll {269 $profilePath = "~/.profile"270 $backupProfilePath = "profile.bak"271 if (Test-Path $profilePath) {272 Move-Item -Path $profilePath -Destination $backupProfilePath -Force273 }274 275 $envVarName = 'PSTEST_PROFILE_LOAD'276 277 $guid = New-Guid278 279 Set-Content -Force -Path $profilePath -Value @"280export $envVarName='$guid'281"@282 }283 284 AfterAll {285 if (Test-Path $backupProfilePath) {286 Move-Item -Path $backupProfilePath -Destination $profilePath -Force287 }288 }289 290 It "Doesn't run the login profile when -Login not used" {291 $result = & $powershell -noprofile -Command "`$env:$envVarName"292 $result | Should -BeNullOrEmpty293 $LASTEXITCODE | Should -Be 0294 }295 296 It "Doesn't falsely recognise -Login when elsewhere in the invocation" {297 $result = & $powershell -nop -c 'Write-Output "-login"'298 $result | Should -BeExactly '-login'299 $LASTEXITCODE | Should -Be 0300 }301 302 It "Doesn't falsely recognise -Login when used after -Command" {303 $result = & $powershell -nop -c 'Write-Output' -Login304 $result | Should -BeExactly '-Login'305 $LASTEXITCODE | Should -Be 0306 }307 308 It "Accepts the <LoginSwitch> switch for -Login and behaves correctly" -TestCases @(309 @{ LoginSwitch = '-l' }310 @{ LoginSwitch = '-L' }311 @{ LoginSwitch = '-login' }312 @{ LoginSwitch = '-Login' }313 @{ LoginSwitch = '-LOGIN' }314 @{ LoginSwitch = '-log' }315 ) {316 param($LoginSwitch)317 318 $result = & $powershell $LoginSwitch -NoProfile -Command "`$env:$envVarName"319 320 if ($IsWindows) {321 $result | Should -BeNullOrEmpty322 $LASTEXITCODE | Should -Be 0323 return324 }325 326 $result | Should -BeExactly $guid327 $LASTEXITCODE | Should -Be 0328 }329 330 It "Starts as a login shell with '-' prepended to name" -Skip:(-not (Get-Command -Name /bin/bash -ErrorAction Ignore)) {331 $quoteEscapedPwsh = $powershell.Replace("'", "\'")332 $pwshCommand = "`$env:$envVarName"333 $bashCommand = "exec -a '-pwsh' '$quoteEscapedPwsh' -NoProfile -Command '`$env:$envVarName' ''"334 $result = /bin/bash -c $bashCommand335 $result | Should -BeExactly $guid336 $LASTEXITCODE | Should -Be 0 # Exit code will be PowerShell's since it was exec'd337 }338 }339 340 Context "-SettingsFile Commandline switch" {341 342 BeforeAll {343 if ($IsWindows) {344 $CustomSettingsFile = Join-Path -Path $TestDrive -ChildPath 'Powershell.test.json'345 $DefaultExecutionPolicy = 'RemoteSigned'346 }347 }348 BeforeEach {349 if ($IsWindows) {350 # reset the content of the settings file to a known state.351 Set-Content -Path $CustomSettingsfile -Value "{`"Microsoft.PowerShell:ExecutionPolicy`":`"$DefaultExecutionPolicy`"}" -ErrorAction Stop352 }353 }354 355 # NOTE: The -settingsFile command-line option only reads settings for the local machine. As a result, the tests that use Set/Get-ExecutionPolicy356 # must use an explicit scope of LocalMachine to ensure the setting is written to the expected file.357 # Skip the tests on Unix platforms because *-ExecutionPolicy cmdlets don't work by design.358 359 It "Verifies PowerShell reads from the custom -settingsFile" -Skip:(!$IsWindows) {360 $actualValue = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {(Get-ExecutionPolicy -Scope LocalMachine).ToString()}361 $actualValue | Should -Be $DefaultExecutionPolicy362 }363 364 It "Verifies PowerShell writes to the custom -settingsFile" -Skip:(!$IsWindows) {365 $expectedValue = 'AllSigned'366 367 # Update the execution policy; this should update the settings file.368 & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope LocalMachine }369 370 # ensure the setting was written to the settings file.371 $content = (Get-Content -Path $CustomSettingsFile | ConvertFrom-Json)372 $content.'Microsoft.PowerShell:ExecutionPolicy' | Should -Be $expectedValue373 374 # ensure the setting is applied on next run375 $actualValue = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {(Get-ExecutionPolicy -Scope LocalMachine).ToString()}376 $actualValue | Should -Be $expectedValue377 }378 379 It "Verify PowerShell removes a setting from the custom -settingsFile" -Skip:(!$IsWindows) {380 # Remove the LocalMachine execution policy; this should update the settings file.381 & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command {Set-ExecutionPolicy -ExecutionPolicy Undefined -Scope LocalMachine }382 383 # ensure the setting was removed from the settings file.384 $content = (Get-Content -Path $CustomSettingsFile | ConvertFrom-Json)385 $content.'Microsoft.PowerShell:ExecutionPolicy' | Should -Be $null386 }387 }388 389 Context "-SettingsFile Commandline switch set 'PSModulePath'" {390 391 BeforeAll {392 $CustomSettingsFile = Join-Path -Path $TestDrive -ChildPath 'powershell.test.json'393 $mPath1 = Join-Path $PSHOME 'Modules'394 $mPath2 = Join-Path $TestDrive 'NonExist'395 $pathSep = [System.IO.Path]::PathSeparator396 397 ## Use multiple paths in the setting.398 $ModulePath = "${mPath1}${pathSep}${mPath2}".Replace('\', "\\")399 Set-Content -Path $CustomSettingsfile -Value "{`"Microsoft.PowerShell:ExecutionPolicy`":`"Unrestricted`", `"PSModulePath`": `"$ModulePath`" }" -ErrorAction Stop400 }401 402 It "Verify PowerShell PSModulePath should contain paths from config file" {403 $psModulePath = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command '$env:PSModulePath'404 405 ## $mPath1 already exists in the value of env PSModulePath, so it won't be added again.406 $index = $psModulePath.IndexOf("${mPath1}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase)407 $index | Should -BeGreaterThan 0408 $index += $mPath1.Length409 $psModulePath.IndexOf($mPath1, $index, [System.StringComparison]::OrdinalIgnoreCase) | Should -BeExactly -1410 411 ## $mPath2 should be added at the index position 0.412 $psModulePath.StartsWith("${mPath2}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) | Should -BeTrue413 }414 }415 416 Context "Pipe to/from powershell" {417 BeforeAll {418 if ($null -ne $PSStyle) {419 $outputRendering = $PSStyle.OutputRendering420 $PSStyle.OutputRendering = 'plaintext'421 }422 423 $p = [PSCustomObject]@{X=10;Y=20}424 }425 426 AfterAll {427 if ($null -ne $PSStyle) {428 $PSStyle.OutputRendering = $outputRendering429 }430 }431 432 It "xml input" {433 $p | & $powershell -noprofile { $input | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } } | Should -Be 30434 $p | & $powershell -noprofile -inputFormat xml { $input | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } } | Should -Be 30435 }436 437 It "text input" {438 # Join (multiple lines) and remove whitespace (we don't care about spacing) to verify we converted to string (by generating a table)439 $p | & $powershell -noprofile -inputFormat text { -join ($input -replace "\s","") } | Should -Be "XY--1020"440 }441 442 It "xml output" {443 & $powershell -noprofile { [PSCustomObject]@{X=10;Y=20} } | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } | Should -Be 30444 & $powershell -noprofile -outputFormat xml { [PSCustomObject]@{X=10;Y=20} } | ForEach-Object {$a = 0} { $a += $_.X + $_.Y } { $a } | Should -Be 30445 }446 447 It "text output" {448 # Join (multiple lines) and remove whitespace (we don't care about spacing) to verify we converted to string (by generating a table)449 -join (& $powershell -noprofile -outputFormat text { $PSStyle.OutputRendering = 'PlainText'; [PSCustomObject]@{X=10;Y=20} }) -replace "\s","" | Should -Be "XY--1020"450 }451 452 It "errors are in text if error is redirected, encoded command, non-interactive, and outputformat specified" {453 $p = [Diagnostics.Process]::new()454 $p.StartInfo.FileName = "pwsh"455 $encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('throw "boom"'))456 $p.StartInfo.Arguments = "-EncodedCommand $encoded -ExecutionPolicy Bypass -NoLogo -NonInteractive -NoProfile -OutputFormat text"457 $p.StartInfo.UseShellExecute = $false458 $p.StartInfo.RedirectStandardError = $true459 $p.Start() | Out-Null460 $out = $p.StandardError.ReadToEnd()461 $out | Should -Not -BeNullOrEmpty462 $out = $out.Split([Environment]::NewLine)[0]463 [System.Management.Automation.Internal.StringDecorated]::new($out).ToString("PlainText") | Should -BeExactly "Exception: boom"464 }465 466 It "Progress is not emitted when stdout is redirected" {467 $ps = [powershell]::Create()468 $null = $ps.AddScript('$a = & ([Environment]::ProcessPath) -Command "Write-Progress -Activity progress"; $a')469 $actual = $ps.Invoke()470 471 $ps.HadErrors | Should -BeFalse472 $actual | Should -BeNullOrEmpty473 $ps.Streams.Progress | Should -BeNullOrEmpty474 }475 476 It "Progress is still emitted with redireciton with XML output" {477 $ps = [powershell]::Create()478 $null = $ps.AddScript('$a = & ([Environment]::ProcessPath) -OutputFormat xml -Command "Write-Progress -Activity progress"; $a')479 $actual = $ps.Invoke()480 481 $ps.HadErrors | Should -BeFalse482 $actual | Should -BeNullOrEmpty483 $ps.Streams.Progress.Count | Should -Be 1484 $ps.Streams.Progress[0].Activity | Should -Be progress485 }486 }487 488 Context "Redirected standard output" {489 It "Simple redirected output" {490 $si = NewProcessStartInfo "-noprofile -c 1+1"491 $process = RunPowerShell $si492 $process.StandardOutput.ReadToEnd() | Should -Be 2493 EnsureChildHasExited $process494 }495 }496 497 Context "Input redirected but not reading from stdin (not really interactive)" {498 # Tests under this context are testing that we do not read from StandardInput499 # even though it is redirected - we want to make sure we don't stop responding.500 # So none of these tests should close StandardInput501 502 It "Redirected input w/ implicit -Command w/ -NonInteractive" {503 $si = NewProcessStartInfo "-NonInteractive -noprofile -c 1+1" -RedirectStdIn504 $process = RunPowerShell $si505 $process.StandardOutput.ReadToEnd() | Should -Be 2506 EnsureChildHasExited $process507 }508 509 It "Redirected input w/ implicit -Command w/o -NonInteractive" {510 $si = NewProcessStartInfo "-noprofile -c 1+1" -RedirectStdIn511 $process = RunPowerShell $si512 $process.StandardOutput.ReadToEnd() | Should -Be 2513 EnsureChildHasExited $process514 }515 516 It "Redirected input w/ explicit -Command w/ -NonInteractive" {517 $si = NewProcessStartInfo "-NonInteractive -noprofile -Command 1+1" -RedirectStdIn518 $process = RunPowerShell $si519 $process.StandardOutput.ReadToEnd() | Should -Be 2520 EnsureChildHasExited $process521 }522 523 It "Redirected input w/ explicit -Command w/o -NonInteractive" {524 $si = NewProcessStartInfo "-noprofile -Command 1+1" -RedirectStdIn525 $process = RunPowerShell $si526 $process.StandardOutput.ReadToEnd() | Should -Be 2527 EnsureChildHasExited $process528 }529 530 It "Redirected input w/ -File w/ -NonInteractive" {531 '1+1' | Out-File -Encoding Ascii -FilePath TestDrive:test.ps1 -Force532 $si = NewProcessStartInfo "-noprofile -NonInteractive -File $testDrive\test.ps1" -RedirectStdIn533 $process = RunPowerShell $si534 $process.StandardOutput.ReadToEnd() | Should -Be 2535 EnsureChildHasExited $process536 }537 538 It "Redirected input w/ -File w/o -NonInteractive" {539 '1+1' | Out-File -Encoding Ascii -FilePath TestDrive:test.ps1 -Force540 $si = NewProcessStartInfo "-noprofile -File $testDrive\test.ps1" -RedirectStdIn541 $process = RunPowerShell $si542 $process.StandardOutput.ReadToEnd() | Should -Be 2543 EnsureChildHasExited $process544 }545 }546 547 Context "Redirected standard input for 'interactive' use" {548 BeforeAll {549 $nl = [Environment]::Newline550 $oldColor = $env:NO_COLOR551 $env:NO_COLOR = 1552 }553 554 AfterAll {555 $env:NO_COLOR = $oldColor556 }557 558 # All of the following tests replace the prompt (either via an initial command or interactively)559 # so that we can read StandardOutput and reliably know exactly what the prompt is.560 561 It "Interactive redirected input: <InteractiveSwitch>" -Pending:($IsWindows) -TestCases @(562 @{InteractiveSwitch = ""}563 @{InteractiveSwitch = " -IntERactive"}564 @{InteractiveSwitch = " -i"}565 ) {566 param($interactiveSwitch)567 $si = NewProcessStartInfo "-noprofile -nologo$interactiveSwitch" -RedirectStdIn568 $process = RunPowerShell $si569 $process.StandardInput.Write("`$function:prompt = { 'PS> ' }`n")570 $null = $process.StandardOutput.ReadLine()571 $process.StandardInput.Write("1+1`n")572 $process.StandardOutput.ReadLine() | Should -Be "PS> 1+1"573 $process.StandardOutput.ReadLine() | Should -Be "2"574 $process.StandardInput.Write("1+2`n")575 $process.StandardOutput.ReadLine() | Should -Be "PS> 1+2"576 $process.StandardOutput.ReadLine() | Should -Be "3"577 578 # Backspace should work as expected579 $process.StandardInput.Write("1+2`b3`n")580 # A real console should render 2`b3 as just 3, but we're just capturing exactly what is written581 $process.StandardOutput.ReadLine() | Should -Be "PS> 1+2`b3"582 $process.StandardOutput.ReadLine() | Should -Be "4"583 $process.StandardInput.Close()584 $process.StandardOutput.ReadToEnd() | Should -Be "PS> "585 EnsureChildHasExited $process586 }587 588 It "Interactive redirected input w/ initial command" -Pending:($IsWindows) {589 $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS> ' }""" -RedirectStdIn590 $process = RunPowerShell $si591 $process.StandardInput.Write("1+1`n")592 $process.StandardOutput.ReadLine() | Should -Be "PS> 1+1"593 $process.StandardOutput.ReadLine() | Should -Be "2"594 $process.StandardInput.Write("1+2`n")595 $process.StandardOutput.ReadLine() | Should -Be "PS> 1+2"596 $process.StandardOutput.ReadLine() | Should -Be "3"597 $process.StandardInput.Close()598 $process.StandardOutput.ReadToEnd() | Should -Be "PS> "599 EnsureChildHasExited $process600 }601 602 It "Redirected input explicit prompting (-File -)" -Pending:($IsWindows) {603 $si = NewProcessStartInfo "-noprofile -" -RedirectStdIn604 $process = RunPowerShell $si605 $process.StandardInput.Write("`$function:prompt = { 'PS> ' }`n")606 $null = $process.StandardOutput.ReadLine()607 $process.StandardInput.Write("1+1`n")608 $process.StandardOutput.ReadLine() | Should -Be "PS> 1+1"609 $process.StandardOutput.ReadLine() | Should -Be "2"610 $process.StandardInput.Close()611 $process.StandardOutput.ReadToEnd() | Should -Be "PS> "612 EnsureChildHasExited $process613 }614 615 It "Redirected input no prompting (-Command -)" -Pending:($IsWindows) {616 $si = NewProcessStartInfo "-noprofile -Command -" -RedirectStdIn617 $process = RunPowerShell $si618 $process.StandardInput.Write("1+1`n")619 $process.StandardOutput.ReadLine() | Should -Be "2"620 621 # Multi-line input622 $process.StandardInput.Write("if (1)`n{`n 42`n}`n`n")623 $process.StandardOutput.ReadLine() | Should -Be "42"624 $process.StandardInput.Write(@"625function foo626{627 'in foo'628}629 630foo631 632"@)633 $process.StandardOutput.ReadLine() | Should -Be "in foo"634 635 # Backspace sent through stdin should be in the final string636 $process.StandardInput.Write("`"a`bc`".Length`n")637 $process.StandardOutput.ReadLine() | Should -Be "3"638 639 # Last command with no newline - should be accepted and640 # produce output after closing stdin.641 $process.StandardInput.Write('22 + 22')642 $process.StandardInput.Close()643 $process.StandardOutput.ReadLine() | Should -Be "44"644 645 EnsureChildHasExited $process646 }647 648 It "Redirected input w/ nested prompt" -Pending:($IsWindows) {649 $si = NewProcessStartInfo "-noprofile -noexit -c ""`$function:prompt = { 'PS' + ('>'*(`$NestedPromptLevel+1)) + ' ' }""" -RedirectStdIn650 $process = RunPowerShell $si651 $process.StandardInput.Write("`$PSStyle.OutputRendering='plaintext'`n")652 $null = $process.StandardOutput.ReadLine()653 $process.StandardInput.Write("`$Host.EnterNestedPrompt()`n")654 $process.StandardOutput.ReadLine() | Should -Be "PS> `$Host.EnterNestedPrompt()"655 $process.StandardInput.Write("exit`n")656 $process.StandardOutput.ReadLine() | Should -Be "PS>> exit"657 $process.StandardInput.Close()658 $process.StandardOutput.ReadToEnd() | Should -Be "PS> "659 EnsureChildHasExited $process660 }661 }662 663 Context "Exception handling" {664 BeforeAll {665 # the default stack size in PowerShell is 10000000, set the stack666 # to something much smaller which will produce the error much faster667 # I saw a reduction from 65 seconds to 79 milliseconds.668 $classDefinition = @'669using System;670using System.Management.Automation;671using System.Management.Automation.Runspaces;672using System.Threading;673namespace StackTest {674 public class StackDepthTest {675 public static PowerShell ps;676 public static int size = 512 * 1024;677 public static void CauseError() {678 Thread t = new Thread(RunPS, size);679 t.Start();680 t.Join();681 }682 public static void RunPS() {683 InitialSessionState iss = InitialSessionState.CreateDefault2();684 iss.ThreadOptions = PSThreadOptions.UseCurrentThread;685 ps = PowerShell.Create(iss);686 ps.AddScript("function recurse { recurse }; recurse").Invoke();687 }688 public static void GetPSError() {689 if ( ps.Streams.Error.Count > 0) {690 throw ps.Streams.Error[0].Exception.InnerException;691 }692 }693 }694}695'@696 $TestType = Add-Type -PassThru -TypeDefinition $classDefinition697 }698 699 It "Should handle a CallDepthOverflow" {700 $TestType::CauseError()701 { $TestType::GetPSError() } | Should -Throw -ErrorId "CallDepthOverflow"702 }703 }704 705 Context "Data, Config, and Cache locations" {706 BeforeEach {707 $XDG_CACHE_HOME = $env:XDG_CACHE_HOME708 $XDG_DATA_HOME = $env:XDG_DATA_HOME709 $XDG_CONFIG_HOME = $env:XDG_CONFIG_HOME710 }711 712 AfterEach {713 $env:XDG_CACHE_HOME = $XDG_CACHE_HOME714 $env:XDG_DATA_HOME = $XDG_DATA_HOME715 $env:XDG_CONFIG_HOME = $XDG_CONFIG_HOME716 }717 718 It "Should start if Data, Config, and Cache location is not accessible" -Skip:($IsWindows) {719 $env:XDG_CACHE_HOME = "/dev/cpu"720 $env:XDG_DATA_HOME = "/dev/cpu"721 $env:XDG_CONFIG_HOME = "/dev/cpu"722 $output = & $powershell -noprofile -Command { (Get-Command).count }723 [int]$output | Should -BeGreaterThan 0724 }725 }726 727 Context "HOME environment variable" {728 It "Should start if HOME is not defined" -Skip:($IsWindows) {729 bash -c "unset HOME;$powershell -c '1+1'" | Should -BeExactly 2730 }731 732 It "Same user should use the same temporary HOME directory for different sessions" -Skip:($IsWindows) {733 $results = bash -c @"734unset HOME;735$powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::DEFAULT)';736$powershell -c '[System.Management.Automation.Platform]::SelectProductNameForDirectory([System.Management.Automation.Platform+XDG_Type]::DEFAULT)';737"@738 $results | Should -HaveCount 2739 $results[0] | Should -BeExactly $results[1]740 741 $tempHomeName = "pwsh-{0}-98288ff9-5712-4a14-9a11-23693b9cd91a" -f [System.Environment]::UserName742 $defaultPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$tempHomeName/.config/powershell"743 $results[0] | Should -BeExactly $defaultPath744 }745 }746 747 Context "PATH environment variable" {748 It "`$PSHOME should be in front so that pwsh.exe starts current running PowerShell" {749 & $powershell -v | Should -Match $PSVersionTable.GitCommitId750 }751 752 It "powershell starts if PATH is not set" -Skip:($IsWindows) {753 bash -c "unset PATH;$powershell -nop -c '1+1'" | Should -BeExactly 2754 }755 }756 757 Context "Ambiguous arguments" {758 It "Ambiguous argument '<testArg>' should return possible matches" -TestCases @(759 @{testArg="-no";expectedMatches=@("-nologo","-noexit","-noprofile","-noninteractive")},760 @{testArg="-format";expectedMatches=@("-inputformat","-outputformat")}761 ) {762 param($testArg, $expectedMatches)763 $output = & $powershell $testArg -File foo 2>&1764 $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter765 $outString = [String]::Join(",", $output)766 foreach ($expectedMatch in $expectedMatches)767 {768 $outString | Should -Match $expectedMatch769 }770 }771 }772 773 Context "-WorkingDirectory parameter" {774 BeforeAll {775 $folderName = (New-Guid).ToString() + " test";776 New-Item -Path ~/$folderName -ItemType Directory777 $ExitCodeBadCommandLineParameter = 64778 }779 780 AfterAll {781 Remove-Item ~/$folderName -Force -ErrorAction SilentlyContinue782 }783 784 It "Can set working directory to '<value>'" -TestCases @(785 @{ value = "~" ; expectedPath = $((Get-Item ~).FullName) },786 @{ value = "~/$folderName"; expectedPath = $((Get-Item ~/$folderName).FullName) },787 @{ value = "~\$folderName"; expectedPath = $((Get-Item ~\$folderName).FullName) }788 ) {789 param($value, $expectedPath)790 $output = & $powershell -NoProfile -WorkingDirectory "$value" -Command '(Get-Location).Path'791 $output | Should -BeExactly $expectedPath792 }793 794 It "Can use '<parameter>' to set working directory" -TestCases @(795 @{ parameter = '-workingdirectory' },796 @{ parameter = '-wd' },797 @{ parameter = '-wo' }798 ) {799 param($parameter)800 $output = & $powershell -NoProfile $parameter ~ -Command "`$PWD.Path"801 $output | Should -BeExactly $((Get-Item ~).FullName)802 }803 804 It "Error case if -WorkingDirectory isn't given argument as last on command line" {805 $output = & $powershell -WorkingDirectory 2>&1806 $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter807 $output | Should -Not -BeNullOrEmpty808 }809 810 It "-WorkingDirectory should be processed before profiles" {811 812 if (Test-Path $PROFILE) {813 $currentProfile = Get-Content $PROFILE814 }815 else {816 New-Item -ItemType File -Path $PROFILE -Force817 }818 819 @"820 (Get-Location).Path821 Set-Location $testdrive822"@ > $PROFILE823 824 try {825 $out = & $powershell -workingdirectory ~ -c '(Get-Location).Path'826 $out | Should -HaveCount 2827 $out[0] | Should -BeExactly (Get-Item ~).FullName828 $out[1] | Should -BeExactly "$testdrive"829 }830 finally {831 if ($currentProfile) {832 Set-Content $PROFILE -Value $currentProfile833 }834 else {835 Remove-Item $PROFILE836 }837 }838 }839 }840 841 Context "CustomPipeName startup tests" {842 843 It "Should create pipe file if CustomPipeName is specified" {844 $pipeName = [System.IO.Path]::GetRandomFileName()845 $pipePath = Get-PipePath $pipeName846 847 # The pipePath should be created by the time the -Command is executed.848 & $powershell -CustomPipeName $pipeName -Command "Test-Path '$pipePath'" | Should -BeTrue849 }850 851 It "Should throw if CustomPipeName is too long on Linux or macOS" -Skip:($IsWindows) {852 # Generate a string that is larger than the max pipe name length.853 $longPipeName = [string]::new("A", 200)854 855 "`$PID" | & $powershell -CustomPipeName $longPipeName -c -856 $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter857 }858 }859 860 Context "ApartmentState WPF tests" -Tag Slow {861 862 It "WPF requires STA and will work" -Skip:(!$IsWindows -or [System.Management.Automation.Platform]::IsNanoServer) {863 Add-Type -AssemblyName presentationframework864 865 $xaml = [xml]@"866 <Window867 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"868 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"869 x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"870 Width = "400" Height = "300" ShowInTaskbar = "True">871 </Window>872"@873 874 $reader = [System.Xml.XmlNodeReader]::new($xaml)875 $Window = [System.Windows.Markup.XamlReader]::Load($reader)876 # This will throw an exception if MTA877 { $Window.Show() } | Should -Not -Throw878 $Window.Close()879 }880 881 }882 883 Context "ApartmentState tests" {884 885 It "Default apartment state for main thread is STA" -Skip:(!$IsWindows -or [System.Management.Automation.Platform]::IsNanoServer) {886 [System.Threading.Thread]::CurrentThread.GetApartmentState() | Should -BeExactly "STA"887 }888 889 It "Default apartment state for new runspace is MTA" -Skip:(!$IsWindows) {890 $ps = [powershell]::Create()891 $ps.AddScript({[System.Threading.Thread]::CurrentThread.GetApartmentState()})892 $ps.Invoke() | Should -BeExactly "MTA"893 }894 895 It "Should be able to set apartment state to: <apartment>" -Skip:(!$IsWindows -or [System.Management.Automation.Platform]::IsNanoServer) -TestCases @(896 @{ apartment = "STA"; switch = "-sta" }897 @{ apartment = "MTA"; switch = "-mta" }898 ) {899 param ($apartment, $switch)900 901 & $powershell $switch -noprofile -command "[System.Threading.Thread]::CurrentThread.GetApartmentState()" | Should -BeExactly $apartment902 }903 904 It "Should fail to set apartment state to: <switch>" -Skip:($IsWindows -and ![System.Management.Automation.Platform]::IsNanoServer) -TestCases @(905 @{ switch = "-sta" }906 @{ switch = "-mta" }907 ) {908 param ($switch)909 910 & $powershell $switch -noprofile -command exit911 $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter912 }913 }914 915 Context "Startup banner text tests" -Tag Slow {916 BeforeAll {917 $outputPath = "Temp:\StartupBannerTest-Output-${Pid}.txt"918 $inputPath = "Temp:\StartupBannerTest-Input.txt"919 "exit" > $inputPath920 921 # Not testing update notification banner text here922 $oldPowerShellUpdateCheck = $env:POWERSHELL_UPDATECHECK923 $env:POWERSHELL_UPDATECHECK = "Off"924 925 # Set TERM to "dumb" to avoid DECCKM codes in the output926 $oldTERM = $env:TERM927 $env:TERM = "dumb"928 929 $escPwd = [regex]::Escape($pwd)930 $expectedPromptPattern = "^PS ${escPwd}> exit`$"931 932 $spArgs = @{933 FilePath = $powershell934 ArgumentList = @("-NoProfile")935 RedirectStandardInput = $inputPath936 RedirectStandardOutput = $outputPath937 WorkingDirectory = $pwd938 PassThru = $true939 NoNewWindow = $true940 UseNewEnvironment = $false941 }942 }943 AfterAll {944 $env:TERM = $oldTERM945 $env:POWERSHELL_UPDATECHECK = $oldPowerShellUpdateCheck946 947 Remove-Item $inputPath -Force -ErrorAction Ignore948 Remove-Item $outputPath -Force -ErrorAction Ignore949 }950 BeforeEach {951 Remove-Item $outputPath -Force -ErrorAction Ignore952 }953 It "Displays expected startup banner text by default" {954 $process = Start-Process @spArgs955 Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue956 957 $out = @(Get-Content $outputPath)958 $out.Count | Should -Be 2959 $out[0] | Should -BeExactly "PowerShell $($PSVersionTable.GitCommitId)"960 $out[1] | Should -MatchExactly $expectedPromptPattern961 }962 It "Displays only the prompt with -NoLogo" {963 $spArgs["ArgumentList"] += "-NoLogo"964 $process = Start-Process @spArgs965 Wait-UntilTrue -sb { $process.HasExited } -TimeoutInMilliseconds 5000 -IntervalInMilliseconds 250 | Should -BeTrue966 967 $out = @(Get-Content $outputPath)968 $out.Count | Should -Be 1969 $out[0] | Should -MatchExactly $expectedPromptPattern970 }971 }972 973 Context 'CommandWithArgs tests' {974 It 'Should be able to run a pipeline with arguments using <param>' -TestCases @(975 @{ param = '-commandwithargs' }976 @{ param = '-cwa' }977 ){978 param($param)979 $out = pwsh -nologo -noprofile $param '$args | % { "[$_]" }' '$fun' '@times'980 $out.Count | Should -Be 2 -Because ($out | Out-String)981 $out[0] | Should -BeExactly '[$fun]'982 $out[1] | Should -BeExactly '[@times]'983 }984 985 It 'Should be able to handle boolean switch: <param>' -TestCases @(986 @{ param = '-switch:$true'; expected = 'True'}987 @{ param = '-switch:$false'; expected = 'False'}988 ){989 param($param, $expected)990 $out = pwsh -nologo -noprofile -cwa 'param([switch]$switch) $switch.IsPresent' $param991 $out | Should -Be $expected992 }993 }994 995 It 'Errors for invalid ExecutionPolicy string' {996 $out = pwsh -nologo -noprofile -executionpolicy NonExistingExecutionPolicy -c 'exit 0' 2>&1997 $out | Should -Not -BeNullOrEmpty998 $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter999 }1000}1001 1002Describe "WindowStyle argument" -Tag Feature {1003 BeforeAll {1004 $defaultParamValues = $PSDefaultParameterValues.Clone()1005 $PSDefaultParameterValues["it:skip"] = !$IsWindows1006 1007 if ($IsWindows)1008 {1009 $ExitCodeBadCommandLineParameter = 641010 Add-Type -Name User32 -Namespace Test -MemberDefinition @"1011public static WINDOWPLACEMENT GetPlacement(IntPtr hwnd)1012{1013 WINDOWPLACEMENT placement = new WINDOWPLACEMENT();1014 placement.length = Marshal.SizeOf(placement);1015 1016 if (!GetWindowPlacement(hwnd, ref placement))1017 {1018 throw new System.ComponentModel.Win32Exception();1019 }1020 1021 return placement;1022}1023 1024[DllImport("user32.dll", SetLastError = true)]1025[return: MarshalAs(UnmanagedType.Bool)]1026public static extern bool GetWindowPlacement(1027 IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);1028 1029[Serializable]1030[StructLayout(LayoutKind.Sequential)]1031public struct WINDOWPLACEMENT1032{1033 public int length;1034 public int flags;1035 public ShowWindowCommands showCmd;1036 public System.Drawing.Point ptMinPosition;1037 public System.Drawing.Point ptMaxPosition;1038 public System.Drawing.Rectangle rcNormalPosition;1039}1040 1041public enum ShowWindowCommands : int1042{1043 Hidden = 0,1044 Normal = 1,1045 Minimized = 2,1046 Maximized = 3,1047}1048"@1049 }1050 }1051 1052 AfterAll {1053 $global:PSDefaultParameterValues = $defaultParamValues1054 }1055 1056 It "-WindowStyle <WindowStyle> should work on Windows" -Pending -TestCases @(1057 @{WindowStyle="Normal"},1058 @{WindowStyle="Minimized"},1059 @{WindowStyle="Maximized"} # hidden doesn't work in CI/Server Core1060 ) {1061 param ($WindowStyle)1062 1063 if (Test-IsWindowsArm64) {1064 Set-ItResult -Pending -Because "All windows are showing up as hidden or ARM64"1065 }1066 1067 try {1068 $ps = Start-Process $powershell -ArgumentList "-WindowStyle $WindowStyle -noexit -interactive" -PassThru1069 $startTime = Get-Date1070 $showCmd = "Unknown"1071 while (((Get-Date) - $startTime).TotalSeconds -lt 10 -and $showCmd -ne $WindowStyle) {1072 Start-Sleep -Milliseconds 1001073 $showCmd = ([Test.User32]::GetPlacement($ps.MainWindowHandle)).showCmd1074 }1075 1076 $showCmd | Should -BeExactly $WindowStyle1077 } finally {1078 $ps | Stop-Process -Force1079 }1080 }1081 1082 It "Invalid -WindowStyle returns error" {1083 & $powershell -WindowStyle invalid1084 $LASTEXITCODE | Should -Be $ExitCodeBadCommandLineParameter1085 }1086}1087 1088Describe "Console host api tests" -Tag CI {1089 Context "String escape and control sequences" {1090 $esc = [char]0x1b1091 $csi = [char]0x9b1092 $testCases =1093 @{InputObject = "abc"; Length = 3; Name = "No escapes"},1094 @{InputObject = "${esc} [31mabc"; Length = 9; Name = "Malformed escape - extra space"},1095 @{InputObject = "${esc}abc"; Length = 4; Name = "Malformed escape - no csi"},1096 @{InputObject = "[31mabc"; Length = 7; Name = "Malformed escape - no escape"}1097 1098 $testCases += if ($Host.UI.SupportsVirtualTerminal)1099 {1100 @{InputObject = "$esc[31mabc"; Length = 3; Name = "Escape at start"}1101 @{InputObject = "$esc[31mabc$esc[0m"; Length = 3; Name = "Escape at start and end"}1102 @{InputObject = "${csi}31mabc"; Length = 3; Name = "C1 CSI at start"}1103 @{InputObject = "${csi}31mabc${csi}0m"; Length = 3; Name = "C1 CSI at start and end"}1104 @{InputObject = "abc${csi}m"; Length = 3; Name = "C1 CSI, no params"}1105 @{InputObject = "abc${csi}#{"; Length = 3; Name = "C1 CSI, XTPUSHSGR"}1106 @{InputObject = "abc${csi}#}"; Length = 3; Name = "C1 CSI, XTPOPSGR"}1107 @{InputObject = "abc${csi}#p"; Length = 3; Name = "C1 CSI, XTPUSHSGR (alias)"}1108 @{InputObject = "abc${csi}#q"; Length = 3; Name = "C1 CSI, XTPOPSGR (alias)"}1109 @{InputObject = "abc${esc}[0#p"; Length = 3; Name = "XTPUSHSGR, with param"}1110 @{InputObject = "${esc}[0;1#qabc"; Length = 3; Name = "XTPOPSGR, with multiple params"}1111 }1112 else1113 {1114 @{InputObject = "$esc[31mabc"; Length = 8; Name = "Escape at start - no virtual term support"}1115 @{InputObject = "$esc[31mabc$esc[0m"; Length = 12; Name = "Escape at start and end - no virtual term support"}1116 @{InputObject = "${csi}31mabc"; Length = 7; Name = "C1 CSI at start - no virtual term support"}1117 @{InputObject = "${csi}31mabc${csi}0m"; Length = 10; Name = "C1 CSI at start and end - no virtual term support"}1118 @{InputObject = "abc${csi}m"; Length = 5; Name = "C1 CSI, no params - no virtual term support"}1119 @{InputObject = "abc${csi}#{"; Length = 6; Name = "C1 CSI, XTPUSHSGR - no virtual term support"}1120 @{InputObject = "abc${csi}#}"; Length = 6; Name = "C1 CSI, XTPOPSGR - no virtual term support"}1121 @{InputObject = "abc${csi}#p"; Length = 6; Name = "C1 CSI, XTPUSHSGR (alias) - no virtual term support"}1122 @{InputObject = "abc${csi}#q"; Length = 6; Name = "C1 CSI, XTPOPSGR (alias) - no virtual term support"}1123 @{InputObject = "abc${esc}[0#p"; Length = 8; Name = "XTPUSHSGR, with param - no virtual term support"}1124 @{InputObject = "${esc}[0;1#qabc"; Length = 10; Name = "XTPOPSGR, with multiple params - no virtual term support"}1125 }1126 1127 It "Should properly calculate buffer cell width of '<Name>'" -TestCases $testCases {1128 param($InputObject, $Length)1129 $Host.UI.RawUI.LengthInBufferCells($InputObject) | Should -Be $Length1130 }1131 }1132}1133 1134Describe "Pwsh exe resources tests" -Tag CI {1135 It "Resource strings are embedded in the executable" -Skip:(!$IsWindows) {1136 $pwsh = Get-Item -Path "$PSHOME\pwsh.exe"1137 $pwsh.VersionInfo.FileVersion | Should -Match $PSVersionTable.PSVersion.ToString().Split("-")[0]1138 $productVersion = $pwsh.VersionInfo.ProductVersion.Replace("-dirty","").Replace(" Commits: ","-").Replace(" SHA: ","-g")1139 if ($PSVersionTable.GitCommitId.Contains("-g")) {1140 $productVersion | Should -BeExactly $PSVersionTable.GitCommitId1141 } else {1142 $productVersion | Should -Match $PSVersionTable.GitCommitId1143 }1144 $pwsh.VersionInfo.ProductName | Should -BeExactly "PowerShell"1145 }1146 1147 It "Manifest contains compatibility section" -Skip:(!$IsWindows) {1148 $osversion = [System.Environment]::OSVersion.Version1149 $PSVersionTable.os | Should -MatchExactly "$($osversion.Major).$($osversion.Minor)"1150 }1151}1152 1153Describe 'Pwsh startup in directories that contain wild cards' -Tag CI {1154 BeforeAll {1155 $powershell = Join-Path -Path $PSHOME -ChildPath "pwsh"1156 $dirnames = "[T]est","[Test","T][est","Test"1157 $testcases = @()1158 foreach ( $d in $dirnames ) {1159 $null = New-Item -type Directory -Path "${TESTDRIVE}/$d"1160 $testcases += @{ Dirname = $d }1161 }1162 }1163 1164 It "pwsh can startup in a directory named <dirname>" -TestCases $testcases {1165 param ( $dirname )1166 try {1167 Push-Location -LiteralPath "${TESTDRIVE}/${dirname}"1168 $result = & $powershell -noprofile -c '(Get-Item .).Name'1169 $result | Should -BeExactly $dirname1170 }1171 finally {1172 Pop-Location1173 }1174 }1175}1176 1177Describe 'Pwsh startup and PATH' -Tag CI {1178 BeforeEach {1179 $oldPath = $env:PATH1180 }1181 1182 AfterEach {1183 $env:PATH = $oldPath1184 }1185 1186 It 'Calling pwsh starts the same version of PowerShell as currently running' {1187 $version = pwsh -v1188 $version | Should -BeExactly "PowerShell $($PSVersionTable.GitCommitId)"1189 }1190 1191 It 'pwsh starts even if PATH is not defined' {1192 $pwsh = Join-Path -Path $PSHOME -ChildPath "pwsh"1193 Remove-Item Env:\PATH1194 $path = & $pwsh -noprofile -command '$env:PATH'1195 $path | Should -BeExactly ($PSHOME + [System.IO.Path]::PathSeparator)1196 }1197}1198 1199Describe 'Console host name' -Tag CI {1200 It 'Name is pwsh' {