Considering the below Powershell code, is there a way to mock $host.ui.PromptForChoice without the internalMenuWrapper function?
<#
.Synopsis
wrap the menu so we can mock calls to it
#>
function internalMenuWrapper {
param (
[Parameter(Mandatory=$true)]
$prompt,
[Parameter(Mandatory=$true)]
$options
)
return = $host.ui.PromptForChoice("Waiting for user input", $prompt, [System.Management.Automation.Host.ChoiceDescription[]]$options, 0)
}
<#
.Synopsis
Create a menu with an array of choices and return the result
#>
function Invoke-Menu($prompt, $opts) {
$options = #()
foreach ($opt in $opts) {
$options += $(new-object System.Management.Automation.Host.ChoiceDescription $opt)
}
$index = internalMenuWrapper $prompt $options
$opts[$index]
}
Describe 'Invoke-Menu' {
Context "when called" {
It "returns the object that was selected" {
#mock fails
Mock internalMenuWrapper { return 0 }
$result = Invoke-Menu "test menu" #("pass", "fail")
$result | Should -Be "pass"
}
}
}
As Mike Shepard points out in a comment, mocking methods isn't supported in Pester, only commands can be mocked (cmdlets, functions, aliases, external programs).
You can work around the issue by using the Get-Host cmdlet instead of $host and mock that:
function Invoke-Menu($prompt, $choices) {
$choiceObjects = [System.Management.Automation.Host.ChoiceDescription[]] $choices
# Use Get-Host instead of $host here; $host cannot be mocked, but Get-Host can.
$index = (Get-Host).ui.PromptForChoice("Waiting for user input", $prompt, $choiceObjects, 0)
$choices[$index]
}
Describe 'Invoke-Menu' {
Context "when called" {
It "returns the object that was selected" {
# Mock Get-Host with a dummy .ui.PromptForChoice() method that instantly
# returns 0 (the first choice).
Mock Get-Host {
[pscustomobject] #{
ui = Add-Member -PassThru -Name PromptForChoice -InputObject ([pscustomobject] #{}) -Type ScriptMethod -Value { return 0 }
}
}
Invoke-Menu 'test menu' '&pass', '&fail' | Should -Be '&pass'
}
}
}
As you point out on GitHub, if the suggested Read-Choice cmdlet is ever implemented (as a PS-friendly wrapper around $host.ui.PromptForChoice()), it could be mocked directly (and there would be no custom code to test).
Related
I am working on writing Pester tests for our PowerShell scripts that are used during task sequences. Several of them work with the task sequence variables and so I wrote a mock that allows for testing reading variables and am now trying to figure out how to do it for writing variables.
This is the code to read a task sequence variable:
$TsEnv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$Value = $TsEnv.Value('VariableNameToRead')
By passing in the $TsEnv to a function I can then mock it with the following:
$TsEnv = #{
'VariableNameToRead' = 'TestValue'
}
Add-Member -InputObject $TsEnv -MemberType ScriptMethod -Name Value -Value {
Param( [String]$Key )
$This[$Key]
}
This is the code for writing a task sequence variable:
$TsEnv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$TsEnv.Value('VariableNameToWrite') = 'ValueToWrite'
With it being in parentheses after the $TsEnv.Value I am thinking it is treating it as a method, but I am unable to find any examples on how to assign values to a method.
With Pester 4.3.3+, you might be able to use New-MockObject to create a usable mock of that COM object.
Alternatively, you can do something similar to the below to allow you to mock the functionality of the COM object.
If that COM object is available on the machines where your CI is running, I might consider skipping the mocks and writing an integration test.
# functions.ps1
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop";
function GetTaskSequenceValue(
[Parameter(Mandatory=$true)]
[string] $varNameToRead,
[Parameter(Mandatory=$false)]
[System.Management.Automation.ScriptBlock] $readAction = {
param([string] $readKey)
$tsEnv = New-Object -COMObject 'Microsoft.SMS.TSEnvironment'
$tsEnv.Value($readKey)
}
) {
$value = Invoke-Command `
-ScriptBlock $readAction `
-ArgumentList #($varNameToRead)
return $value
}
function SetTaskSequenceValue(
[Parameter(Mandatory=$true)]
[string] $varNameToWrite,
[Parameter(Mandatory=$false)]
[System.Management.Automation.ScriptBlock] $writeAction = {
param([string] $writeKey, [string] $value)
$tsEnv = New-Object -COMObject 'Microsoft.SMS.TSEnvironment'
$TsEnv.Value($writeKey) = $value
}
) {
try {
Invoke-Command `
-ScriptBlock $writeAction `
-ArgumentList #($varNameToWrite)
return $true
}
catch {
# Swallow it
}
return $false
}
Tests for the functions abov. The tests manually mock out the COM objects
# functions.test.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop";
Describe "GetTaskSequenceValue" {
It "gets the expected value" {
$expected = 'value'
$mockAction = {
param($dummy)
return 'value'
}
$actual = GetTaskSequenceValue `
-varNameToRead 'dummyName' `
-readAction $mockAction
$actual | Should Be $expected
}
}
Describe "SetTaskSequenceValue" {
It "sets the expected value" {
$expected = 'value'
$mockAction = {
param($dummy)
return 'value'
}
$actual = SetTaskSequenceValue `
-varNameToWrite 'dummyValue' `
-writeAction $mockAction
$actual | Should Be $true
}
}
Anything to deal with getting environment variables, WMI, or dotnet static method calls, I like to contain within a small helper function, then it's very easy to mock it. Here's what that helper could look like.
Function Get-SMSTsVariable{($VariableName)
return $TSEnv.Value($VariableName)
}
Then you can easily mock this in various contexts to check and see how your code acts when various environmental variables are set.
For instance, if you want it to return a value of BitLockerProvisioning when you run Get-SMSTsVariable -VariableName _SMSTSCurrentActionName, and to return 'C:' when you run _OSDDetectedWinDir you setup a mock like this:
mock Get-SMSTsVariable `
-parameterFilter { $VariableName -eq '_SMSTSCurrentActionName'} `
-mockWith {return 'BitLockerProvisioning'}
mock Get-SMSTsVariable `
-parameterFilter { $VariableName -eq '_OSDDetectedWinDir'} `
-mockWith {return 'C:'}
In this way, you can begin your test setting up a handful of responses for the various ways your functions operate. It's really a breeze.
If I have the following (simplified Setup)
class.ps1
class Test {
Test() {
$MyInvocation | Show-Object
}
[void] Call() {
$MyInvocation | Show-Object
<#
here $MyInvocation.ScriptName, PSCommandPath show main.ps1 NOT
util.ps1 even though it is called from Util.ps1
#>
}
}
Util.ps1
Write-Host "Calling from Util.ps1"
$MyInvocation | Show-Object
Function Test-Util {
[CmdletBinding()]
Param()
Write-Host "Calling from Test-Util in Util.ps1"
$MyInvocation | Show-Object
}
Function Test-Class {
[CmdletBinding()]
Param()
write-host "Testing Class Test from Util.ps1"
$Test = [Test]::new()
Write-Host "Testing Class.Call() from Util.ps1"
$Test.Call()
}
Function Test-SubUtilTest {
[CmdletBinding()]
Param()
Test-SubUtil
}
SubUtil.ps1
Write-Host "Calling from SubUtil.ps1"
$MyInvocation | Show-Object
Function Test-SubUtil {
[CmdletBinding()]
Param()
Write-Host "Calling from Test-Util in Util.ps1"
$MyInvocation | Show-Object
<#
here $MyInvocation.ScriptName, PSCommandPath show Util.ps1 NOT
main.ps1 as it is called from Util.ps1
#>
}
Main.ps1
. C:\Users\jenny\Class.ps1
. C:\Users\jenny\Util.ps1
. C:\Users\jenny\SubUtil.ps1
Write-Host "From Main.ps1"
$MyInvocation | Show-Object
write-host "Calling Test-Util from util.ps1"
Test-Util
Write-Host "Calling Test-Class from util.ps1"
Test-Class
write-host "Calling Test-SubUtil from Util.ps1"
Test-SubUtilTest
$Test = [Test]::new()
and
$Test.Call()
are both executed from util.ps1
yet the $MyInvocation only shows me main.ps1
How do I from either the Constructor of the class or one of its methods determine the ps1 file its calling code originates from when its in this kind of a nested dot source setup
I have tried swapping to & instead of . and I've also tried just moving the dot source of the class.ps1 to the util.ps1 file but it still tells me main.ps1 is the source.
Plus the real class file is a singleton meant to be used across multiple dot sourced util.ps1 files and I'm not sure if I can dot source the same class file in multiple files that are each dot sourced into main.ps1 (not that moving the dot source into the single utility file in this example made a difference)
Finally I'm using Show-Object from the PowerShellCookbook module.
It seems odd that it works for a function based nested call but not for a call to a class
You could use the stack to determine the caller:
class Test {
Test() {
Get-PSCallStack | Select-Object -First 1 -Skip 1 -ExpandProperty "Location" | Write-Host
}
[void] Call() {
Get-PSCallStack | Select-Object -First 1 -Skip 1 -ExpandProperty "Location" | Write-Host
}
}
mhu deserves full credit and the official answer check mark for giving me the answer. But I figured I'd post my fixed classes with the implemented behavior for anyone else who happens to need to do what I had to
Enum LogEntryTypes {
Information
Error
Verbose
Warning
}
Log.psm1
Class Log {
[string] $Name
hidden [string] $FullPath
hidden Log([string] $Name, [string] $Path) {
$this.Name = $Name
$this.FullPath = (Join-Path -Path $Path -ChildPath "$($this.Name).log")
}
[Log] Start([bool] $Append) {
if (-not $Append) { remove-item -Path $this.FullPath -Force -Verbose }
$this.Information("$($this.Name) Logging Started to $($this.FullPath)")
return $this
}
[void] Stop() { $this.Information("$($this.Name) Logging Ended to $($this.FullPath)") }
Information([string] $Message) { $this.Information($Message,$null) }
Information([string] $Message, [object] $Data) { $this.Write($Message,$Data,[LogEntryTypes]::Information) }
Warning([string] $Message) { $this.Warning($Message,$null) }
Warning([string] $Message, [object] $Data) { $this.Write($Message,$Data,[LogEntryTypes]::Warning) }
Error([string] $Message) { $this.Error($Message,$null) }
Error([string] $Message, [object] $Data) { $this.Write($Message,$Data,[LogEntryTypes]::Error) }
Verbose([string] $Message) { $this.Verbose($Message,$null) }
Verbose([string] $Message, [object] $Data) { $this.Write($Message,$Data,[LogEntryTypes]::Verbose) }
[void] hidden Write([string] $Message, [object] $Data, [LogEntryTypes] $EntryType) {
$Message = $Message -replace '"', '`"'
"[$($EntryType.ToString().ToUpper())]<$([DateTime]::Now)>:`tMessage->$Message" | Add-Content -Path $this.FullPath
if ($Data -ne $null) { "[$($EntryType.ToString().ToUpper())]<$([DateTime]::Now)>:`tData->$Data" | Add-Content -Path $this.FullPath }
"Write-$EntryType `"[$($EntryType.ToString().ToUpper())]<$([DateTime]::Now)>:`tMessage->$Message`"" | Invoke-Expression
if ($Data -ne $null) { "Write-$EntryType `"[$($EntryType.ToString().ToUpper())]<$([DateTime]::Now)>:`tData->$Data`"" | Invoke-Expression }
}
}
Logger.ps1
using namespace System.Collections.Generic
using module ".\Log.psm1"
Class Logger {
hidden static [Dictionary[String,Log]] $_Logger
static [string] $Path
static [bool] $Append
static Logger() { [Logger]::_Logger = [Dictionary[string,Log]]::new() }
hidden static [string] GetCallingScriptName() { return ((Get-PSCallStack | Where-Object {$_.Location -notlike "$(((Get-PSCallStack | Select-Object -First 1 -ExpandProperty Location) -split "\.ps1")[0])*" } | Select-Object -First 1 -ExpandProperty "Location") -split "\.ps1")[0] }
static [Log] Get() { return [Logger]::Get($false) }
static [Log] Get([bool] $Force) {
$Name = [Logger]::GetCallingScriptName()
if ($null -eq [Logger]::_Logger[$Name] -or $Force) {
[Logger]::_Logger[$Name] = [Log]::new($Name,[Logger]::Path).Start([Logger]::Append)
}
return [Logger]::_Logger[$Name]
}
static Setup([string] $Path) { [Logger]::Setup($Path,$true) }
static Setup([string] $Path, [bool] $Append) {
[Logger]::Path = $Path
[Logger]::Append = $Append
}
}
thanks to mhu it's pretty simple I can now use this class from any script file by calling
[Logger]::Get().<Entry Type Method>() the Get() either opens the existing log created for the script or creates a new log
Here is an example of what I am trying to do.
Function Get-Parameters { Echo $SomeMagicMethod.Get("Name"); }
Get-Parameters -Name "John Doe"
$SomeMagicMethod is an automatic variable or any other method to get named undeclared parameters.
Is that possible in Powershell?
You'll have to parse the unbounded arguments yourself and find the argument that's right after whatever parameter name you're looking for.
I'd abstract it away in a separate function (you could also pass $args, but this is cleaner):
function Get-InvocationParameter
{
param(
[Parameter(Mandatory = $true, Position = 0)]
[System.Management.Automation.InvocationInfo]
$Invocation,
[Parameter(Mandatory = $true, Position = 1)]
[string]
$ParameterName
)
$Arguments = $Invocation.UnboundArguments
$ParamIndex = $Arguments.IndexOf("-$ParameterName")
if($ParamIndex -eq -1){
return
}
return $Arguments[$ParamIndex + 1]
}
Then use it like this:
function Get-Parameter
{
Get-InvocationParameter -Invocation $MyInvocation -ParameterName "Name"
}
And you should be able to see the arguments right after -Name (or nothing):
PS C:\> Get-Parameter -Name "John Doe"
John Doe
PS C:\> Get-Parameter -Name "John Doe","Jane Doe"
John Doe
Jane Doe
PS C:\> Get-Parameter -ID 123
PS C:\>
You can define a special parameter to catch all unbound arguments:
Function Get-Parameters {
Param(
[Parameter(Mandatory=$true)]
$SomeParam,
[Parameter(Mandatory=$false)]
$OtherParam = 'something',
...
[Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)]
$OtherArgs
)
...
}
However, that will give you an array with the remaining arguments. There won't be an association between -Name and "John Doe".
If your function doesn't define any other parameters you could use the automatic variable $args to the same end.
If you want some kind of hashtable with the unbound "named" arguments you need to build that yourself, e.g. like this:
$UnboundNamed = #{}
$UnboundUnnamed = #()
$OtherArgs | ForEach-Object {
if ($_ -like '-*') {
$script:named = $_ -replace '^-'
$UnboundNamed[$script:named] = $null
} elseif ($script:named) {
$UnboundNamed[$script:named] = $_
$script:name = $null
} else {
$UnboundUnnamed += $_
}
}
If you truly want a function with no parameters, $args is the way to go. (Why you would want such a thing is a different question.) Anyway, code like the following will parse the $args array into a hashtable of parameter/argument pairs which you can use in the rest of the function body.
function NoParams
{
$myParams = #{}
switch ($args)
{
-Foo {
if (!$switch.MoveNext()) {throw "Missing argument for Foo"}
$myParams.Foo = $switch.Current
}
-Bar {
if (!$switch.MoveNext()) {throw "Missing argument for Bar"}
$myParams.Bar = $switch.Current
}
-Baz {
if (!$switch.MoveNext()) {throw "Missing argument for Baz"}
$myParams.Baz = $switch.Current
}
default { throw "Invalid parameter '$_'" }
}
$myParams
}
I have a pipe function that allocates some resources in begin block that need to be disposed at the end. I've tried doing it in the end block but it's not called when function execution is aborted for example by ctrl+c.
How would I modify following code to ensure that $sw is always disposed:
function Out-UnixFile([string] $Path, [switch] $Append) {
<#
.SYNOPSIS
Sends output to a file encoded with UTF-8 without BOM with Unix line endings.
#>
begin {
$encoding = new-object System.Text.UTF8Encoding($false)
$sw = new-object System.IO.StreamWriter($Path, $Append, $encoding)
$sw.NewLine = "`n"
}
process { $sw.WriteLine($_) }
# FIXME not called on Ctrl+C
end { $sw.Close() }
}
EDIT: simplified function
Unfortunately, there is no good solution for this. Deterministic cleanup seems to be a glaring omission in PowerShell. It could be as simple as introducing a new cleanup block that is always called regardless of how the pipeline ends, but alas, even version 5 seems to offer nothing new here (it introduces classes, but without cleanup mechanics).
That said, there are some not-so-good solutions. Simplest, if you enumerate over the $input variable rather than use begin/process/end you can use try/finally:
function Out-UnixFile([string] $Path, [switch] $Append) {
<#
.SYNOPSIS
Sends output to a file encoded with UTF-8 without BOM with Unix line endings.
#>
$encoding = new-object System.Text.UTF8Encoding($false)
$sw = $null
try {
$sw = new-object System.IO.StreamWriter($Path, $Append, $encoding)
$sw.NewLine = "`n"
foreach ($line in $input) {
$sw.WriteLine($line)
}
} finally {
if ($sw) { $sw.Close() }
}
}
This has the big drawback that your function will hold up the entire pipeline until everything is available (basically the whole function is treated as a big end block), which is obviously a deal breaker if your function is intended to process lots of input.
The second approach is to stick with begin/process/end and manually process Control-C as input, since this is really the problematic bit. But by no means the only problematic bit, because you also want to handle exceptions in this case -- end is basically useless for purposes of cleanup, since it is only invoked if the entire pipeline is successfully processed. This requires an unholy mix of trap, try/finally and flags:
function Out-UnixFile([string] $Path, [switch] $Append) {
<#
.SYNOPSIS
Sends output to a file encoded with UTF-8 without BOM with Unix line endings.
#>
begin {
$old_treatcontrolcasinput = [console]::TreatControlCAsInput
[console]::TreatControlCAsInput = $true
$encoding = new-object System.Text.UTF8Encoding($false)
$sw = new-object System.IO.StreamWriter($Path, $Append, $encoding)
$sw.NewLine = "`n"
$end = {
[console]::TreatControlCAsInput = $old_treatcontrolcasinput
$sw.Close()
}
}
process {
trap {
&$end
break
}
try {
if ($break) { break }
$sw.WriteLine($_)
} finally {
if ([console]::KeyAvailable) {
$key = [console]::ReadKey($true)
if (
$key.Modifiers -band [consolemodifiers]"control" -and
$key.key -eq "c"
) {
$break = $true
}
}
}
}
end {
&$end
}
}
Verbose as it is, this is the shortest "correct" solution I can come up with. It does go through contortions to ensure the Control-C status is restored properly and we never attempt to catch an exception (because PowerShell is bad at rethrowing them); the solution could be slightly simpler if we didn't care about such niceties. I'm not even going to try to make a statement about performance. :-)
If someone has ideas on how to improve this, I'm all ears. Obviously checking for Control-C could be factored out to a function, but beyond that it seems hard to make it simpler (or at least more readable) because we're forced to use the begin/process/end mold.
It's possible to write it in C# where one can implement IDisposable - confirmed to be called by powershell in case of ctrl-c.
I'll leave the question open in case someone comes up with some way of doing it in powershell.
using System;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Text;
namespace MarcWi.PowerShell
{
[Cmdlet(VerbsData.Out, "UnixFile")]
public class OutUnixFileCommand : PSCmdlet, IDisposable
{
[Parameter(Mandatory = true, Position = 0)]
public string FileName { get; set; }
[Parameter(ValueFromPipeline = true)]
public PSObject InputObject { get; set; }
[Parameter]
public SwitchParameter Append { get; set; }
public OutUnixFileCommand()
{
InputObject = AutomationNull.Value;
}
public void Dispose()
{
if (sw != null)
{
sw.Close();
sw = null;
}
}
private StreamWriter sw;
protected override void BeginProcessing()
{
base.BeginProcessing();
var encoding = new UTF8Encoding(false);
sw = new StreamWriter(FileName, Append, encoding);
sw.NewLine = "\n";
}
protected override void ProcessRecord()
{
sw.WriteLine(InputObject);
}
protected override void EndProcessing()
{
base.EndProcessing();
Dispose();
}
}
}
The following is an implementation of "using" for PowerShell (from Solutionizing .Net). using is a reserved word in PowerShell, hence the alias PSUsing:
function Using-Object {
param (
[Parameter(Mandatory = $true)]
[Object]
$inputObject = $(throw "The parameter -inputObject is required."),
[Parameter(Mandatory = $true)]
[ScriptBlock]
$scriptBlock
)
if ($inputObject -is [string]) {
if (Test-Path $inputObject) {
[system.reflection.assembly]::LoadFrom($inputObject)
} elseif($null -ne (
new-object System.Reflection.AssemblyName($inputObject)
).GetPublicKeyToken()) {
[system.reflection.assembly]::Load($inputObject)
} else {
[system.reflection.assembly]::LoadWithPartialName($inputObject)
}
} elseif ($inputObject -is [System.IDisposable] -and $scriptBlock -ne $null) {
Try {
&$scriptBlock
} Finally {
if ($inputObject -ne $null) {
$inputObject.Dispose()
}
Get-Variable -scope script |
Where-Object {
[object]::ReferenceEquals($_.Value.PSBase, $inputObject.PSBase)
} |
Foreach-Object {
Remove-Variable $_.Name -scope script
}
}
} else {
$inputObject
}
}
New-Alias -Name PSUsing -Value Using-Object
With example usage:
psusing ($stream = new-object System.IO.StreamReader $PSHOME\types.ps1xml) {
foreach ($_ in 1..5) { $stream.ReadLine() }
}
Obviously this is really just some packaging around Jeroen's first answer but may be useful for others who find their way here.
I am trying to rename a few of my cmdlets and want to do it without breaking existing scripts. I want to do it without using Set-Alias/New-Alias because I do not want the Aliases to show up when we do Get-Command from the powershell prompt and I thought it might be possible to use exported functions to achieve the same thing that aliasing cmdlets would do.
Here is an example of what I want to do
Old cmdlet - Add-Foo
Renamed cmdlet - Add-FooBar
Expectation - Scripts using Add-Foo should continue to work the same way as it used to
I am thinking of introducing the following function
function Add-Foo()
{
# Delegate parameter inputs to cmdlet Add-FooBar
}
I have a simple version of it but I am not sure if it would work in more complex cases.
function Add-Foo()
{
$cmd = "Add-FooBar"
if ($arguments.Length -eq 0){
Invoke-Expression $cmd;
}
else{
# Concatentate cmdlet and arguments into an expression
$expr = "$($cmd) $($args)";
Write-Debug $expr;
Invoke-Expression $expr;
}
}
I am not sure if my function is going to be 100% compatible with existing usages. Can the function Add-Foo be made such that it behaves well with parameter attributes (pipeline binding) and any other possible usages? Essentially I want the function to take the arguments as is and pass it to the underlying renamed cmdlet.
Any help is appreciated.
Thanks
PowerShell has a built-in feature for this: Proxy commands.
The [System.Management.Automation.ProxyCommand] class has several static methods to help out with this. Below is a template you can use to generate a proxy command and add a condition choosing whether or not to call the original command.
function New-ProxyCommand($command)
{
$cmd = Get-Command $command
$blocks = #{
CmdletBinding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($cmd)
Params = [System.Management.Automation.ProxyCommand]::GetParamBlock($cmd)
Begin = [System.Management.Automation.ProxyCommand]::GetBegin($cmd)
Process = [System.Management.Automation.ProxyCommand]::GetProcess($cmd)
End = [System.Management.Automation.ProxyCommand]::GetEnd($cmd)
}
# Indent
filter Indent($indent=' ') { $_ | foreach { ($_ -split "`r`n" | foreach { "${indent}$_" }) -join "`r`n" } }
[array]$blocks.Keys | foreach { $blocks[$_] = $blocks[$_] | Indent }
#"
function $command
{
$($blocks.CmdletBinding)
param
($($blocks.Params)
)
begin
{
`$Reroute = `$false ### Put your conditions here ###
if (`$Reroute) { return }
$($blocks.Begin)}
process
{
if (`$Reroute) { return }
$($blocks.Process)}
end
{
if (`$Reroute) { return }
$($blocks.End)}
}
"#
}
Example:
PS> New-ProxyCommand Get-Item
function Get-Item
{
[CmdletBinding(DefaultParameterSetName='Path', SupportsTransactions=$true, HelpUri='http://go.microsoft.com/fwlink/?LinkID=113319')]
param
(
[Parameter(ParameterSetName='Path', Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]
${Path},
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
${LiteralPath},
[string]
${Filter},
[string[]]
${Include},
[string[]]
${Exclude},
[switch]
${Force},
[Parameter(ValueFromPipelineByPropertyName=$true)]
[pscredential]
[System.Management.Automation.CredentialAttribute()]
${Credential}
)
begin
{
$Reroute = $false ### Put your conditions here ###
if ($Reroute) { return }
try {
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-Item', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
if ($Reroute) { return }
try {
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
if ($Reroute) { return }
try {
$steppablePipeline.End()
} catch {
throw
}
}
}
One option is to use a private function:
function Private:Add-Foo
{
Add-Foobar $args
}
Add-Foo will only call this function in the current scope. The function will not be visible within any child scope (like a called script), and they will use the Add-Foo cmdlet instead.