diff --git a/examples/PathProcessing.Tests.ps1 b/examples/PathProcessing.Tests.ps1 new file mode 100644 index 0000000000..fd7ae4efaf --- /dev/null +++ b/examples/PathProcessing.Tests.ps1 @@ -0,0 +1,88 @@ +New-Item -Path 'foo[1].txt' -Force + +. $PSScriptRoot\PathProcessingNonExistingPaths.ps1 +Describe 'Verify Path Processing for Non-existing Paths Allowed Impl' { + It 'Processes non-wildcard absolute path to non-existing file via -Path param' { + New-File -Path $PSScriptRoot\ReadmeNew.md | Should Be "$PSScriptRoot\READMENew.md" + } + It 'Processes multiple absolute paths via -Path param' { + New-File -Path $PSScriptRoot\Readme.md, $PSScriptRoot\XYZZY.ps1 | + Should Be @("$PSScriptRoot\README.md", "$PSScriptRoot\XYZZY.ps1") + } + It 'Processes relative path via -Path param' { + New-File -Path ..\examples\READMENew.md | Should Be "$PSScriptRoot\READMENew.md" + } + It 'Processes multiple relative path via -Path param' { + New-File -Path ..\examples\README.md, XYZZY.ps1 | + Should Be @("$PSScriptRoot\README.md", "$PSScriptRoot\XYZZY.ps1") + } + + It 'Should accept pipeline input to Path' { + Get-ChildItem -LiteralPath "$pwd\foo[1].txt" | New-File | Should Be "$PSScriptRoot\foo[1].txt" + } +} + +. $PSScriptRoot\PathProcessingNoWildcards.ps1 +Describe 'Verify Path Processing for NO Wildcards Allowed Impl' { + It 'Processes non-wildcard absolute path via -Path param' { + Import-FileNoWildcard -Path $PSScriptRoot\Readme.md | Should Be "$PSScriptRoot\README.md" + } + It 'Processes multiple absolute paths via -Path param' { + Import-FileNoWildcard -Path $PSScriptRoot\Readme.md, $PSScriptRoot\PathProcessingWildcards.ps1 | + Should Be @("$PSScriptRoot\README.md", "$PSScriptRoot\PathProcessingWildcards.ps1") + } + It 'Processes relative path via -Path param' { + Import-FileNoWildcard -Path ..\examples\README.md | Should Be "$PSScriptRoot\README.md" + } + It 'Processes multiple relative path via -Path param' { + Import-FileNoWildcard -Path ..\examples\README.md, .vscode\launch.json | + Should Be @("$PSScriptRoot\README.md", "$PSScriptRoot\.vscode\launch.json") + } + + It 'Should accept pipeline input to Path' { + Get-ChildItem -LiteralPath "$pwd\foo[1].txt" | Import-FileNoWildcard | Should Be "$PSScriptRoot\foo[1].txt" + } +} + +. $PSScriptRoot\PathProcessingWildcards.ps1 +Describe 'Verify Path Processing for Wildcards Allowed Impl' { + It 'Processes non-wildcard absolute path via -Path param' { + Import-FileWildcard -Path $PSScriptRoot\Readme.md | Should Be "$PSScriptRoot\README.md" + } + It 'Processes multiple absolute paths via -Path param' { + Import-FileWildcard -Path $PSScriptRoot\Readme.md, $PSScriptRoot\PathProcessingWildcards.ps1 | + Should Be @("$PSScriptRoot\README.md", "$PSScriptRoot\PathProcessingWildcards.ps1") + } + It 'Processes wildcard absolute path via -Path param' { + Import-FileWildcard -Path $PSScriptRoot\*.md | Should Be "$PSScriptRoot\README.md" + } + It 'Processes wildcard relative path via -Path param' { + Import-FileWildcard -Path *.md | Should Be "$PSScriptRoot\README.md" + } + It 'Processes relative path via -Path param' { + Import-FileWildcard -Path ..\examples\README.md | Should Be "$PSScriptRoot\README.md" + } + It 'Processes multiple relative path via -Path param' { + Import-FileWildcard -Path ..\examples\README.md, .vscode\launch.json | + Should Be @("$PSScriptRoot\README.md", "$PSScriptRoot\.vscode\launch.json") + } + + It 'DefaultParameterSet should be Path' { + Import-FileWildcard *.md | Should Be "$PSScriptRoot\README.md" + } + + It 'Should process absolute literal paths via -LiteralPath param'{ + Import-FileWildcard -LiteralPath "$PSScriptRoot\foo[1].txt" | Should Be "$PSScriptRoot\foo[1].txt" + } + It 'Should process relative literal paths via -LiteralPath param'{ + Import-FileWildcard -LiteralPath "..\examples\foo[1].txt" | Should Be "$PSScriptRoot\foo[1].txt" + } + It 'Should process multiple literal paths via -LiteralPath param'{ + Import-FileWildcard -LiteralPath "..\examples\foo[1].txt", "$PSScriptRoot\README.md" | + Should Be @("$PSScriptRoot\foo[1].txt", "$PSScriptRoot\README.md") + } + + It 'Should accept pipeline input to LiteralPath' { + Get-ChildItem -LiteralPath "$pwd\foo[1].txt" | Import-FileWildcard | Should Be "$PSScriptRoot\foo[1].txt" + } +} diff --git a/examples/PathProcessingNoWildcards.ps1 b/examples/PathProcessingNoWildcards.ps1 new file mode 100644 index 0000000000..8773db94b7 --- /dev/null +++ b/examples/PathProcessingNoWildcards.ps1 @@ -0,0 +1,46 @@ +function Import-FileNoWildcard { + [CmdletBinding(SupportsShouldProcess=$true)] + param( + # Specifies a path to one or more locations. + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName="Path", + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Path to one or more locations.")] + [Alias("PSPath")] + [ValidateNotNullOrEmpty()] + [string[]] + $Path + ) + + begin { + } + + process { + # Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true)] + $paths = @() + foreach ($aPath in $Path) { + if (!(Test-Path -LiteralPath $aPath)) { + $ex = New-Object System.Management.Automation.ItemNotFoundException "Cannot find path '$aPath' because it does not exist." + $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errRecord = New-Object System.Management.Automation.ErrorRecord $ex,'PathNotFound',$category,$aPath + $psCmdlet.WriteError($errRecord) + continue + } + + # Resolve any relative paths + $paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath) + } + + foreach ($aPath in $paths) { + if ($pscmdlet.ShouldProcess($aPath, 'Operation')) { + # Process each path + $aPath + } + } + } + + end { + } +} \ No newline at end of file diff --git a/examples/PathProcessingNonExistingPaths.ps1 b/examples/PathProcessingNonExistingPaths.ps1 new file mode 100644 index 0000000000..d8a06e1ecc --- /dev/null +++ b/examples/PathProcessingNonExistingPaths.ps1 @@ -0,0 +1,38 @@ +function New-File { + [CmdletBinding(SupportsShouldProcess=$true)] + param( + # Specifies a path to one or more locations. + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName="Path", + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Path to one or more locations.")] + [Alias("PSPath")] + [ValidateNotNullOrEmpty()] + [string[]] + $Path + ) + + begin { + } + + process { + # Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true)] + $paths = @() + foreach ($aPath in $Path) { + # Resolve any relative paths + $paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath) + } + + foreach ($aPath in $paths) { + if ($pscmdlet.ShouldProcess($aPath, 'Operation')) { + # Process each path + $aPath + } + } + } + + end { + } +} \ No newline at end of file diff --git a/examples/PathProcessingWildcards.ps1 b/examples/PathProcessingWildcards.ps1 new file mode 100644 index 0000000000..bc88d2d0bb --- /dev/null +++ b/examples/PathProcessingWildcards.ps1 @@ -0,0 +1,77 @@ +function Import-FileWildcard { + [CmdletBinding(SupportsShouldProcess=$true, DefaultParameterSetName='Path')] + param( + # Specifies a path to one or more locations. Wildcards are permitted. + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName="Path", + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true, + HelpMessage="Path to one or more locations.")] + [ValidateNotNullOrEmpty()] + [SupportsWildcards()] + [string[]] + $Path, + + # Specifies a path to one or more locations. Unlike the Path parameter, the value of the LiteralPath parameter is + # used exactly as it is typed. No characters are interpreted as wildcards. If the path includes escape characters, + # enclose it in single quotation marks. Single quotation marks tell Windows PowerShell not to interpret any + # characters as escape sequences. + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName="LiteralPath", + ValueFromPipelineByPropertyName=$true, + HelpMessage="Literal path to one or more locations.")] + [Alias("PSPath")] + [ValidateNotNullOrEmpty()] + [string[]] + $LiteralPath + ) + + begin { + } + + process { + # Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true, DefaultParameterSetName='Path')] + $paths = @() + if ($psCmdlet.ParameterSetName -eq 'Path') { + foreach ($aPath in $Path) { + if (!(Test-Path -Path $aPath)) { + $ex = New-Object System.Management.Automation.ItemNotFoundException "Cannot find path '$aPath' because it does not exist." + $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errRecord = New-Object System.Management.Automation.ErrorRecord $ex,'PathNotFound',$category,$aPath + $psCmdlet.WriteError($errRecord) + continue + } + + # Resolve any wildcards that might be in the path + $provider = $null + $paths += $psCmdlet.SessionState.Path.GetResolvedProviderPathFromPSPath($aPath, [ref]$provider) + } + } + else { + foreach ($aPath in $LiteralPath) { + if (!(Test-Path -LiteralPath $aPath)) { + $ex = New-Object System.Management.Automation.ItemNotFoundException "Cannot find path '$aPath' because it does not exist." + $category = [System.Management.Automation.ErrorCategory]::ObjectNotFound + $errRecord = New-Object System.Management.Automation.ErrorRecord $ex,'PathNotFound',$category,$aPath + $psCmdlet.WriteError($errRecord) + continue + } + + # Resolve any relative paths + $paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath) + } + } + + foreach ($aPath in $paths) { + if ($pscmdlet.ShouldProcess($aPath, 'Operation')) { + # Process each path + $aPath + } + } + } + + end { + } +} \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index f7ee154c72..3f2c4cf281 100644 --- a/examples/README.md +++ b/examples/README.md @@ -49,11 +49,40 @@ Try these steps: 9. Observe that every time the breakpoint is hit, the watch variables get updated. 10. When you're done debugging, click the red **Stop** button or press `Shift+F5` -If you would like to debug a different script, you will need to edit the -`.vscode\launch.json` file and change the `program` parameter to point to -the script file to be debugged. In the future we hope to remove the -necessity of this setting so that the current script file will be executed -when `F5` is pressed. +The debugger will attempt to execute the file in the active editor pane. +If you would like to configure a single script to always be executed upon +launch of the debugger, you will need to edit the `.vscode\launch.json` +file and change the `program` parameter to point to the script file to be +debugged. The path must be absolute but you can use the ${workspaceRoot} variable +to refer to the open folder in VSCode e.g. +`"program": "${workspaceRoot}\\DebugTest.ps1"` + +### Passing Arguments to the Script + +If you would like to pass arguments to your script, open the `.vscode\launch.json` +file in your workspace and modify the `args` parameter e.g.: + +`"args": [ "-Param1 foo -Recurse" ]` + +You can pass all your script arguments in a single string or break them up +into individual strings e.g.: + +`"args": [ "-Param1", "foo" "-Recurse" ],` + +At runtime these arguments will be concatenated togehter using a space +delimiter so it will result in the same string as the first `args` example. + +### Setting the Working Directory + +When the debugger starts it will set the working directory of the PowerShell +environment depending on the value of the `cwd` parameter in the +`.vscode\launch.json` file in your workspace. If this parameter is missing or +is set to an empty string, the working directory will be set to the workspace directory. +By default it is set to `${file}` which will set the working directory to the parent +directory of the file in the active editor pane when the debugger is launched. +You can also set the parameter explicitly e.g.: + +`"cwd": "C:\\Users\\JSnover\\Documents\\MonadUberAlles"` ## Feedback diff --git a/snippets/PowerShell.json b/snippets/PowerShell.json index 7b181cf64d..c62fb99f06 100644 --- a/snippets/PowerShell.json +++ b/snippets/PowerShell.json @@ -209,37 +209,41 @@ ], "description": "Example: function-based DSC resource provider snippet" }, - "Example-Path Processing": { - "prefix": "ex-path processing", + "Example-Path Processing for Non-Existing Paths": { + "prefix": "ex-path processing for non-existing paths", "body": [ "# Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true)]", + "$paths = @()", "foreach ($aPath in $Path) {", + "\t# Resolve any relative paths", + "\t$paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath)", + "}", + "", + "foreach ($aPath in $paths) {", "\tif ($pscmdlet.ShouldProcess($aPath, 'Operation')) {", "\t\t# Process each path", "\t\t$0", "\t}", "}" ], - "description": "Example: processing paths allowing for non-existing paths (for use in process block). See parameter-path snippets." + "description": "Example: processing non-existing paths typically used in New-* commands (for use in process block). See parameter-path snippet." }, - "Example-Path Must Exist Processing": { - "prefix": "ex-path must exist processing", + "Example-Path Processing for No Wildcards Allowed": { + "prefix": "ex-path processing for no wildcards allowed", "body": [ "# Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true)]", "$paths = @()", - "if ($psCmdlet.ParameterSetName -eq 'Path') {", - "\tforeach ($aPath in $Path) {", - "\t\t# If Path doesn't have to exist, eg this command creates a file, remove the following if statement.", - "\t\tif (!(Test-Path -Path $aPath)) {", - "\t\t\t$ex = New-Object System.Management.Automation.ItemNotFoundException \"Cannot find path '$aPath' because it does not exist.\"", - "\t\t\t$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound", - "\t\t\t$errRecord = New-Object System.Management.Automation.ErrorRecord $ex,'PathNotFound',$category,$aPath", - "\t\t\t$psCmdlet.WriteError($errRecord)", - "\t\t\tcontinue", - "\t\t}", - "\t", - "\t\t$paths += $aPath", + "foreach ($aPath in $Path) {", + "\tif (!(Test-Path -LiteralPath $aPath)) {", + "\t\t$ex = New-Object System.Management.Automation.ItemNotFoundException \"Cannot find path '$aPath' because it does not exist.\"", + "\t\t$category = [System.Management.Automation.ErrorCategory]::ObjectNotFound", + "\t\t$errRecord = New-Object System.Management.Automation.ErrorRecord $ex,'PathNotFound',$category,$aPath", + "\t\t$psCmdlet.WriteError($errRecord)", + "\t\tcontinue", "\t}", + "", + "\t# Resolve any relative paths", + "\t$paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath)", "}", "", "foreach ($aPath in $paths) {", @@ -249,10 +253,10 @@ "\t}", "}" ], - "description": "Example: processing paths that must exist (for use in process block). See parameter-path snippets." + "description": "Example: processing non-wildcard paths that must exist (for use in process block). See parameter-path snippets." }, - "Example-Path with Wildcards Processing": { - "prefix": "ex-path wildcard processing", + "Example-Path Processing for Wildcards Allowed": { + "prefix": "ex-path processing for wildcards allowed", "body": [ "# Modify [CmdletBinding()] to [CmdletBinding(SupportsShouldProcess=$true, DefaultParameterSetName='Path')]", "$paths = @()", @@ -266,8 +270,9 @@ "\t\t\tcontinue", "\t\t}", "\t", - "\t\t# In the -Path (non-literal) case, resolve any wildcards in path", - "\t\t$paths += $aPath | Resolve-Path | Foreach Path", + "\t\t# Resolve any wildcards that might be in the path", + "\t\t$provider = $null", + "\t\t$paths += $psCmdlet.SessionState.Path.GetResolvedProviderPathFromPSPath($aPath, [ref]$provider)", "\t}", "}", "else {", @@ -280,7 +285,8 @@ "\t\t\tcontinue", "\t\t}", "\t", - "\t\t$paths += $aPath", + "\t\t# Resolve any relative paths", + "\t\t$paths += $psCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($aPath)", "\t}", "}", "", @@ -291,7 +297,7 @@ "\t}", "}" ], - "description": "Example: processing paths with wildcards (for use in process block). See parameter-path-wildcards and parameter-literalpath snippets." + "description": "Example: processing wildcard paths that must exist (for use in process block). See parameter-path-wildcards and parameter-literalpath snippets." }, "Example-Splatting": { "prefix": "ex-splat", @@ -499,23 +505,24 @@ "Parameter-Path": { "prefix": "parameter-path", "body": [ - "# Specifies a path to one or more locations. The default location is the current directory (.).", + "# Specifies a path to one or more locations.", "[Parameter(Mandatory=$true,", " Position=${Position:0},", " ParameterSetName=\"${ParameterSetName:Path}\",", " ValueFromPipeline=$true,", " ValueFromPipelineByPropertyName=$true,", " HelpMessage=\"Path to one or more locations.\")]", + "[Alias(\"PSPath\")]", "[ValidateNotNullOrEmpty()]", "[string[]]", "$${ParameterName:Path}$0" ], - "description": "Parameter declaration snippet for Path parameter that does not accept wildcards" + "description": "Parameter declaration snippet for Path parameter that does not accept wildcards. Do not use with parameter-literalpath." }, "Parameter-Path-Wildcards": { "prefix": "parameter-path-wildcards", "body": [ - "# Specifies a path to one or more locations. Wildcards are permitted. The default location is the current directory (.).", + "# Specifies a path to one or more locations. Wildcards are permitted.", "[Parameter(Mandatory=$true,", " Position=${Position:0},", " ParameterSetName=\"${ParameterSetName:Path}\",", @@ -527,7 +534,7 @@ "[string[]]", "$${ParameterName:Path}$0" ], - "description": "Parameter declaration snippet for Path parameter that accepts wildcards" + "description": "Parameter declaration snippet for Path parameter that accepts wildcards. Add parameter-literalpath to handle paths with embedded wildcard chars." }, "Parameter-LiteralPath": { "prefix": "parameter-literalpath",