test/ie.Tests.ps1
Set-StrictMode -Version 1 $ErrorActionPreference = 'Stop' # For older WinPS versions: Set OS/edition flags (which in PSCore are automatically defined). # !! At least with Pester v5.x, script-level variables must explicitly created with scope $script: # !! Do NOT *refer to* these variables with $script: below, however. if (-not (Test-Path Variable:IsWindows)) { $script:IsWindows = $true } if (-not (Test-Path Variable:IsCoreCLR)) { $script:IsCoreCLR = $false } # Force-(re)import this module. # Target the *.psd1 file explicitly, so the tests can run from versioned subfolders too. Note that the # loaded module's ModuleInfo's .Path property will reflect the *.psm1 instead. $manifest = (Get-Item $PSScriptRoot/../*.psd1) Remove-Module -ea Ignore -Force $manifest.BaseName # Note: To be safe, we unload any modules with the same name first (they could be in a different location and end up side by side in memory with this one.) Import-Module $manifest -Force -Global # -Global makes sure that when psake runs tester in a child scope, the module is still imported globally. Describe 'ie tests' { Context "PlatformNeutral" { It 'Only permits calls to external executables' { # Note: `whoami` exists on both Windows and Unix-like platforms. { ie whoami } | Should -Not -Throw # An *alias* of an external executable is allowed too. # Note: `-Scope Script` is required here for the test to see the alias. { set-alias -Scope Script exealias whoami; ie exealias } | Should -Not -Throw # No other command forms should be accepted: aliases of non-executables, functions, cmdlets. { ie select } | Should -Throw -ErrorId ApplicationNotFoundException { ie cd.. } | Should -Throw -ErrorId ApplicationNotFoundException { ie Get-Date } | Should -Throw -ErrorId ApplicationNotFoundException } It 'Properly passes arguments to external executables' { # Note: Avoid arguments with embedded newlines, because dbea -Raw # doesn't support them due to line-by-line output. $exeArgs = '', 'a&b', '3 " of snow', 'Nat "King" Cole', 'c:\temp 1\', 'a b\\', 'a \" b', 'a \"b c\" d', 'a"b', 'ab\' $result = dbea -Raw -UseIe -- $exeArgs Compare-Object $exeArgs $result | ForEach-Object { '{0} <{1}>' -f $_.SideIndicator, $_.InputObject } | Should -BeNull } It 'Properly passes scripts with complex quoting to various interpreters (if installed)' { $ohtCmds = [ordered] @{ # CLIs that require \" and must be escaped that way in WinPS too. ruby = { ie ruby -e 'puts "hi there"' } perl = { ie perl -E 'say "hi there"' } Rscript = { ie Rscript -e 'print("hi there")' } pwsh = { ie pwsh -noprofile -c '"hi there"' } powershell = { ie powershell -noprofile -c '"hi there"' } # CLIs that also accept "" and are used with that escaping in *WinPS* node = { ie node -pe '"hi there"' } python = { ie python -c 'print("hi there")' } } foreach ($exe in $ohtCmds.Keys) { if (Get-Command -ea Ignore -Type Application $exe) { "Testing with $exe...." | Write-Verbose -vb $expected = if ($exe -eq 'Rscript') { '[1] "hi there"' } else { 'hi there' } & $ohtCmds[$exe] | Should -BeExactly $expected } } } } Context "Windows" -Skip:(-not $IsWindows) { It 'Handles batch-file quoting needs with space-less arguments' { # cmd.exe metachars. (in addition to ") that must trigger "..." enclosure: # & | < > ^ , ; $exeArgs = 'a"b', 'a&b', 'a|b', 'a<b', 'a>b', 'a^b', 'a,b', 'a;b', 'last' # Note: Batch file always echo arguments exactly as quoted. $expected = '"a""b"', '"a&b"', '"a|b"', '"a<b"', '"a>b"', '"a^b"', '"a,b"', '"a;b"', 'last' -split (dbea -UseIe -UseBatchFile -Raw -- $exeArgs) | Should -BeExactly $expected } It 'Handles partial-quoting needs of CLIs such as msiexec.exe' { # Arguments of the following form must be placed with *partial double-quoting*, # *around the value only* on the command line (e.g., `foo="bar none"`): $argsToPartiallyQuote = if ($PSVersionTable.PSVersion.Major -le 4) { # !! CAVEAT: In WinPS v3 and v4 only, *a partially quoted value that contains spaces*, such as `foo="bar none"`, # !! causes the engine to still enclose the entire argument in "...", which cannot be helped. # !! Only a space-less value that needs quoting is handled correctly (which may never occur in practice). Write-Warning "Partial quoting of msiexec-style arguments with spaces not supported in v3 and v4, skipping test." 'foo=bar"none' } else { # !! In PS Core, verbatim `foo=bar"none` would only become `'foo="bar""none"` if the executable were msiexec.exe, msdeploy.exe, or cmdkey.exe; # !! otherwise - as in these tests - \"-escaping kicks in and `foo=bar"none` becomes `foo=bar\"none` instead - without syntactic double-quoting around the value. 'foo=bar none', '/foo:bar none', '-foo:bar none', 'foo=bar "stuff" none' + ('foo=bar"none', @())[$IsCoreCLR] } $argsToPartiallyQuote | ForEach-Object { $exeArgs = $_, 'a " b' # Add a second argument. # The value part be selectively quoted. # ""-escaping must also be triggered (it's what msiexec requires). # !! In WinPS we use ""-escaping by default anyway, to work around legacy bugs, # !! but in *PS Core* we default to \"-escaping, because it is the safer choice; as a nod to # !! these high-profile CLIs, we have *hard-coded exceptions for 'msiexece' and 'msdeploy' there. # !! We therefore can't test this aspect on PS Core. A manual way to do it is to create (temporary) # !! copies of de.exe, name them 'msiexec.exe' and 'msdeploy.exe', then call them with `ie`; e.g.: # !! # !! 'msiexec.exe', 'msdeploy.exe' | % { cpi -ea stop (gcm de.exe).Path "./$_"; ie "./$_" 'foo=bar none', '/foo:bar none', '-foo:bar none', 'foo=bar "stuff" none', 'foo=bar"none'; ri "./$_" } # !! $partiallyQuotedArg = (($_ -replace '"', ('""', '\"')[$IsCoreCLR]) -replace '(?<=[:=]).+$', '"$&"') $expectedRawCmdLine = '{0} {1}' -f $partiallyQuotedArg, ('"a "" b"', '"a \" b"')[$IsCoreCLR] # Run dbea and extract the raw command line from the output (last non-blank line.) $rawCmdLine = ((dbea -UseIe -- $exeArgs) -notmatch '^\s*$')[-1].Trim() $rawCmdLine | Should -BeExactly $expectedRawCmdLine } } } } |