How can I invoke a scriptblock in the caller's context? - function

Consider the following call site:
$modifiedLocal = 'original local value'
'input object' | SomeScriptblockInvoker {
$modifiedLocal = 'modified local value'
[pscustomobject] #{
Local = $local
DollarBar = $_
}
}
$modifiedLocal
I would like to implement SomeScriptblockInvoker such that
it is defined in a module, and
the scriptblock is invoked in the caller's context.
The output of the function at the call site would be the following:
Local DollarBar
----- ---------
local input object
modified local value
PowerShell seems to be capable of doing this. For example replacing SomeScriptblockInvoker with ForEach-Object yields exactly the desired output.
I have come close using the following definition:
New-Module m {
function SomeScriptblockInvoker {
param
(
[Parameter(Position = 1)]
[scriptblock]
$Scriptblock,
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
$InputObject | . $Scriptblock
}
}
} |
Import-Module
The output of the call site using that definition is the following:
Local DollarBar
----- ---------
local
modified local value
Note that DollarBar is empty when it should be input object.
(gist of Pester tests to check for correct behavior)

In general, you can't. The caller of a scriptblock does not have control over the SessionState associated with that scriptblock and that SessionState determines (in part) the context in which a scriptblock is executed (see the Scope section for details). Depending on where the scriptblock is defined, its SessionState might match the caller's context, but it might not.
Scope
With respect to the context in which the scriptblock is executed, there are two related considerations:
The SessionState associated with the scriptblock.
Whether or not the calling method adds a scope to the SessionState's scope stack.
Here is a good explanation of how this works.
The $_ Automatic Variable
$_ contains the current object in the pipeline. The scriptblock provided to % is interpreted differently from the scriptblock provided . and &:
'input_object' | % {$_} - The value of $_ is 'input_object' because the scriptblock is bound to %'s -Process parameter. That scriptblock is executed once for each object in the pipeline.
'input_object' | . {process{$_}} and 'input_object' | & {process{$_}} - The value of $_ is 'input_object' because the $_ in the scriptblock is inside a process{} block which executes once for each object in the pipeline.
Note that in the OP $_ was empty when the scriptblock was invoked using .. This is because the scriptblock contained no process{} block. Each of the statements in the scriptblock were implicitly part of the scriptblock's end{} block. By the time an end{} block is run, there is no longer any object in the pipeline and $_ is null.
. vs & vs %
., &, and % each invoke the scriptblock using the SessionState of the scriptblock with some differences according to the following table:
+---+-----------------+-----------+-------------------+----------------------+
| | Name | Kind | interprets {} as | adds scope to stack |
+---+-----------------+-----------+-------------------+----------------------+
| % | ForEach-Object | Command | Process block | No |
| . | dot-source | Operator | scriptblock | No |
| & | call | Operator | scriptblock | Yes |
+---+-----------------+-----------+-------------------+----------------------+
The % command has other parameters corresponding to Begin{} and End{} blocks.
For variable assignments made by the scriptblock to have side-effects on the SessionState associated with the scriptblock, use an invocation method that does not add a scope to the stack. Otherwise, the variable assignment will only affect the newly-created scope and disappear once the scriptblock completes execution.
The Most Viable Options
The two most viable options for passing the tests in OP are as follows. Note that neither invokes the scriptblock strictly in the caller's context but rather in a context using the SessionState associated with the scriptblock.
Option 1
Change the call site so that the scriptblock includes process{}:
$modifiedLocal = 'original local value'
'input object' | SomeScriptblockInvoker {
process {
$modifiedLocal = 'modified local value'
[pscustomobject] #{
Local = $local
DollarBar = $_
}
}
}
$modifiedLocal
And invoke the scriptblock using SomeScriptblockInvoker in OP.
Option 2
Invoke the scriptblock using % as suggested by PetSerAl.

Related

How to get a lengthy string environment variable in full?

The command gci env:ApiSecret | ConvertTo-Json works to return a long string, the API secret for Twitter, which is truncated without the pipe to JSON.
However, the JSON is rather spammy.
Is there a "goldilocks" way to get the lengthy string value without the extraneous details?
(Unfortunately, gci env: truncates the key)
Get-ChildItem is for retrieving all or a subset of items from a container. Note that it outputs an object with Name and Value properties (substituting Path as another lengthy environment variable value)...
PS> gci env:Path
Name Value
---- -----
Path C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\WINDO...
Get-Item yields the same result...
PS> gi env:Path
Name Value
---- -----
Path C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\WINDO...
Either way, the object retrieved is a DictionaryEntry...
PS> gi env:Path | Get-Member
TypeName: System.Collections.DictionaryEntry
Name MemberType Definition
---- ---------- ----------
Name AliasProperty Name = Key
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
PSDrive NoteProperty PSDriveInfo PSDrive=Env
PSIsContainer NoteProperty bool PSIsContainer=False
PSPath NoteProperty string PSPath=Microsoft.PowerShell.Core\Environment::path
PSProvider NoteProperty ProviderInfo PSProvider=Microsoft.PowerShell.Core\Environment
Key Property System.Object Key {get;set;}
Value Property System.Object Value {get;set;}
...and when you pipe that to ConvertTo-Json it will include all kinds of undesirable properties from that class.
In short, don't use ConvertTo-Json for this. Since you know the exact item you want, just retrieve it directly using variable syntax...
PS> $env:Path
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
Equivalent code using the .NET API would be...
PS> [Environment]::GetEnvironmentVariable('Path')
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
If you really wanted to use a Get-*Item cmdlet you'd just need to specify that it's the Value property you want using property syntax...
PS> (gi env:Path).Value
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
...or Select-Object...
PS> gi env:Path | Select-Object -ExpandProperty 'Value'
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;...
All of the above commands will output only a [String] containing the entirety of that environment variable value. I inserted trailing ellipses since showing my entire Path value is not useful here; in practice, those commands will output the entire environment variable with no truncation.
The simplest way to inspect the value of environment variables in full is to use the $env:<varName> (namespace variable notation) syntax, which in your case means: $env:ApiSecret (if the variable name contains special characters, enclose everything after the $ in {...}; e.g., ${env:ApiSecret(1)})
That way, environment-variable values (which are invariably strings) that are longer than your terminal's (console's) width simply continue on subsequent lines.
To demonstrate:
# Simulate a long value (200 chars.)
$env:ApiSecret = 'x' * 199 + '!'
# Output the long value
$env:ApiSecret
With an 80-char. wide terminal, you'd see output as follows:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx!
If you do want to use Get-Item (or Get-ChildItem, which acts the same in this case), you have two options:
# Format-List shows each property on its own line,
# with values wrapping across multiple lines
Get-Item env:ApiSecret | Format-List
# Format-Table -Wrap causes values to wrap as well.
Get-Item env:ApiSecret | Format-Table -Wrap
Your statement does not strip anything away. However, for console display purpose, it truncate the output that you view in the console.
If you assign the result to a variable or pipe to a file, nothing will be truncated.
Therefore, my assumption on your question is that you want to view the result in the console without the console truncating your stuff there.
For that, you could write the results to the host yourself.
Here's a simple example that do just that.
$envvars = gci env:
$Max = ($envvars.name| Measure-Object -Property length -Maximum).Maximum + 3
$envvars | % {Write-Host $_.name.padright($Max,' ') -ForegroundColor Cyan -NoNewline;Write-Host $_.value}
Result — As you can see, the path variable value is no longer truncated.

Are there 2 kinds of $null in PowerShell? [duplicate]

Apparently, in PowerShell (ver. 3) not all $null's are the same:
>function emptyArray() { #() }
>$l_t = #() ; $l_t.Count
0
>$l_t1 = #(); $l_t1 -eq $null; $l_t1.count; $l_t1.gettype()
0
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
>$l_t += $l_t1; $l_t.Count
0
>$l_t += emptyArray; $l_t.Count
0
>$l_t2 = emptyArray; $l_t2 -eq $null; $l_t2.Count; $l_t2.gettype()
True
0
You cannot call a method on a null-valued expression.
At line:1 char:38
+ $l_t2 = emptyArray; $l_t2 -eq $null; $l_t2.Count; $l_t2.gettype()
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
>$l_t += $l_t2; $l_t.Count
0
>$l_t3 = $null; $l_t3 -eq $null;$l_t3.gettype()
True
You cannot call a method on a null-valued expression.
At line:1 char:32
+ $l_t3 = $null; $l_t3 -eq $null;$l_t3.gettype()
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
>$l_t += $l_t3; $l_t.count
1
>function addToArray($l_a, $l_b) { $l_a += $l_b; $l_a.count }
>$l_t = #(); $l_t.Count
0
>addToArray $l_t $l_t1
0
>addToArray $l_t $l_t2
1
So how and why is $l_t2 different from $l_t3? In particular, is $l_t2 really $null or not? Note that $l_t2 is NOT an empty array ($l_t1 is, and $l_t1 -eq $null returns nothing, as expected), but neither is it truly $null, like $l_t3. In particular, $l_t2.count returns 0 rather than an error, and furthermore, adding $l_t2 to $l_t behaves like adding an empty array, not like adding $null. And why does $l_t2 suddenly seem to become "more $null" when it gets passed in the the function addToArray as a parameter???????
Can anyone explain this behaviour, or point me to documentation that would explain it?
Edit:
The answer by PetSerAl below is correct. I have also found this stackOverflow post on the same issue.
Powershell version info:
>$PSVersionTable
Name Value
---- -----
WSManStackVersion 3.0
PSCompatibleVersions {1.0, 2.0, 3.0}
SerializationVersion 1.1.0.1
BuildVersion 6.2.9200.16481
PSVersion 3.0
CLRVersion 4.0.30319.1026
PSRemotingProtocolVersion 2.2
In particular, is $l_t2 really $null or not?
$l_t2 is not $null, but a [System.Management.Automation.Internal.AutomationNull]::Value. It is a special instance of PSObject. It is returned when a pipeline returns zero objects. That is how you can check it:
$a=&{} #shortest, I know, pipeline, that returns zero objects
$b=[System.Management.Automation.Internal.AutomationNull]::Value
$ReferenceEquals=[Object].GetMethod('ReferenceEquals')
$ReferenceEquals.Invoke($null,($a,$null)) #returns False
$ReferenceEquals.Invoke($null,($a,$b)) #returns True
I call ReferenceEquals thru Reflection to prevent conversion from AutomationNull to $null by PowerShell.
$l_t1 -eq $null returns nothing
For me it returns an empty array, as I expect from it.
$l_t2.count returns 0
It is a new feature of PowerShell v3:
You can now use Count or Length on any object, even if it didn’t have the property. If the object didn’t have a Count or Length property, it will will return 1 (or 0 for $null). Objects that have Count or Length properties will continue to work as they always have.
PS> $a = 42
PS> $a.Count
1
 
And why does $l_t2 suddenly seem to become "more $null" when it gets passed in the the function addToArray as a parameter???????
It seems that PowerShell converts AutomationNull to $null in some cases, like calling .NET methods. In PowerShell v2, even when saving AutomationNull to a variable it gets converted to $null.
To complement PetSerAl's great answer with a pragmatic summary:
Commands that happen to produce no output do not return $null, but the [System.Management.Automation.Internal.AutomationNull]::Value singleton,
which can be thought of as an "array-valued $null" or, to coin a term, null enumeration. It is sometimes also called "AutomationNull", for its type name.
Note that, due to PowerShell's automatic enumeration of collections, even a command that explicitly outputs an empty collection object such as #() has no output (unless enumeration is explicitly prevented, such as with Write-Output -NoEnumerate).
In short, this special value behaves like $null in scalar contexts, and like an empty array in enumeration contexts, notably in the pipeline, as the examples below demonstrate.
Given that $null and the null enumeration situationally behave differently, distinguishing between the two via reflection may be necessary, which is currently far from trivial; GitHub issue #13465 proposes implementing a test that would allow you to use $someValue -is [AutomationNull].
As of PowerShell 7.3.0, the following, obscure test is required:
$null -eq $someValue -and $someValue -is [psobject]
Caveats:
Passing [System.Management.Automation.Internal.AutomationNull]::Value as a cmdlet / function parameter value invariably converts it to $null.
See GitHub issue #9150.
In PSv3+, even an actual (scalar) $null is not enumerated in a foreach loop; it is enumerated in a pipeline, however - see bottom.
In PSv2-, saving a null enumeration in a variable quietly converted it to $null and $null was enumerated in a foreach loop as well (not just in a pipeline) - see bottom.
# A true $null value:
$trueNull = $null
# An operation with no output returns
# the [System.Management.Automation.Internal.AutomationNull]::Value singleton,
# which is treated like $null in a scalar expression context,
# but behaves like an empty array in a pipeline or array expression context.
$automationNull = & {} # calling (&) an empty script block ({}) produces no output
# In a *scalar expression*, [System.Management.Automation.Internal.AutomationNull]::Value
# is implicitly converted to $null, which is why all of the following commands
# return $true.
$null -eq $automationNull
$trueNull -eq $automationNull
$null -eq [System.Management.Automation.Internal.AutomationNull]::Value
& { param($param) $null -eq $param } $automationNull
# By contrast, in a *pipeline*, $null and
# [System.Management.Automation.Internal.AutomationNull]::Value
# are NOT the same:
# Actual $null *is* sent as data through the pipeline:
# The (implied) -Process block executes once.
$trueNull | % { 'input received' } # -> 'input received'
# [System.Management.Automation.Internal.AutomationNull]::Value is *not* sent
# as data through the pipeline, it behaves like an empty array:
# The (implied) -Process block does *not* execute (but -Begin and -End blocks would).
$automationNull | % { 'input received' } # -> NO output; effectively like: #() | % { 'input received' }
# Similarly, in an *array expression* context
# [System.Management.Automation.Internal.AutomationNull]::Value also behaves
# like an empty array:
(#() + $automationNull).Count # -> 0 - contrast with (#() + $trueNull).Count, which returns 1.
# CAVEAT: Passing [System.Management.Automation.Internal.AutomationNull]::Value to
# *any parameter* converts it to actual $null, whether that parameter is an
# array parameter or not.
# Passing [System.Management.Automation.Internal.AutomationNull]::Value is equivalent
# to passing true $null or omitting the parameter (by contrast,
# passing #() would result in an actual, empty array instance).
& { param([object[]] $param)
[Object].GetMethod('ReferenceEquals').Invoke($null, #($null, $param))
} $automationNull # -> $true; would be the same with $trueNull or no argument at all.
The [System.Management.Automation.Internal.AutomationNull]::Value documentation states:
Any operation that returns no actual value should return AutomationNull.Value.
Any component that evaluates a Windows PowerShell expression should be prepared to deal with receiving and discarding this result. When received in an evaluation where a value is required, it should be replaced with null.
PSv2 vs. PSv3+, and general inconsistencies:
PSv2 offered no distinction between [System.Management.Automation.Internal.AutomationNull]::Value and $null for values stored in variables:
Using a no-output command directly in a foreach statement / pipeline did work as expected - nothing was sent through the pipeline / the foreach loop wasn't entered:
Get-ChildItem nosuchfiles* | ForEach-Object { 'hi' }
foreach ($f in (Get-ChildItem nosuchfiles*)) { 'hi' }
By contrast, if a no-output commands was saved in a variable or an explicit $null was used, the behavior was different:
# Store the output from a no-output command in a variable.
$result = Get-ChildItem nosuchfiles* # PSv2-: quiet conversion to $null happens here
# Enumerate the variable.
$result | ForEach-Object { 'hi1' }
foreach ($f in $result) { 'hi2' }
# Enumerate a $null literal.
$null | ForEach-Object { 'hi3' }
foreach ($f in $null) { 'hi4' }
PSv2: all of the above commands output a string starting with hi, because $null is sent through the pipeline / being enumerated by foreach:
Unlike in PSv3+, [System.Management.Automation.Internal.AutomationNull]::Value is converted to $null on assigning to a variable, and $null is always enumerated in PSv2.
PSv3+: The behavior changed in PSv3, both for better and worse:
Better: Nothing is sent through the pipeline for the commands that enumerate $result: The foreach loop is not entered, because the [System.Management.Automation.Internal.AutomationNull]::Value is preserved when assigning to a variable, unlike in PSv2.
Possibly Worse: foreach no longer enumerates $null (whether specified as a literal or stored in a variable), so that foreach ($f in $null) { 'hi4' } perhaps surprisingly produces no output.
On the plus side, the new behavior no longer enumerates uninitialized variables, which evaluate to $null (unless prevented altogether with Set-StrictMode).
Generally, however, not enumerating $null would have been more justified in PSv2, given its inability to store the null-collection value in a variable.
In summary, the PSv3+ behavior:
takes away the ability to distinguish between $null and [System.Management.Automation.Internal.AutomationNull]::Value in the context of a foreach statement
thereby introduces an inconsistency with pipeline behavior, where this distinction is respected.
For the sake of backward compatibility, the current behavior cannot be changed. This comment on GitHub proposes a way to resolve these inconsistencies for a (hypothetical) potential future PowerShell version that needn't be backward-compatible.
When you return a collection from a PowerShell function, by default PowerShell determines the data type of the return value as follows:
If the collection has more than one element, the return result is an array. Note that the data type of the return result is System.Array even if the object being returned is a collection of a different type.
If the collection has a single element, the return result is the value of that element, rather than a collection of one element, and the data type of the return result is the data type of that element.
If the collection is empty, the return result is $null
$l_t = #() assigns an empty array to $l_t.
$l_t2 = emptyArray assigns $null to $l_t2, because the function emptyArray returns an empty collection, and therefore the return result is $null.
$l_t2 and $l_t3 are both null, and they behave the same way. Since you've pre-declared $l_t as an empty array, when you add either $l_t2 or $l_t3 to it, either with the += operator or the addToArray function, an element whose value is **$null* is added to the array.
If you want to force the function to preserve the data type of the collection object you're returning, use the comma operator:
PS> function emptyArray {,#()}
PS> $l_t2 = emptyArray
PS> $l_t2.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
PS> $l_t2.Count
0
Note: The empty parentheses after emtpyArray in the function declaration is superfluous. You only need parentheses after the function name if you're using them to declare parameters.
An interesting point to be aware of is that the comma operator doesn't necessarily make the return value an array.
Recall that as I mentioned in the first bullet point, by default the data type of the return result of a collection with more than one element is System.Array regardless of the actual data type of the collection. For example:
PS> $list = New-Object -TypeName System.Collections.Generic.List[int]
PS> $list.Add(1)
PS> $list.Add(2)
PS> $list.Count
2
PS> $list.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
Note that the data type of this collection is List`1, not System.Array.
However, if you return it from a function, within the function the data type of $list is List`1, but it's returned as a System.Array containing the same elements.
PS> function Get-List {$list = New-Object -TypeName System.Collections.Generic.List[int]; $list.Add(1); $list.Add(2); return $list}
PS> $l = Get-List
PS> $l.Count
2
PS> $l.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
If you want the return result to be a collection of the same data type as the one within the function that you're returning, the comma operator will accomplish that:
PS> function Get-List {$list = New-Object -TypeName System.Collections.Generic.List[int]; $list.Add(1); $list.Add(2); return ,$list}
PS> $l = Get-List
PS> $l.Count
2
PS> $l.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
This isn't limited to array-like collection objects. As far as I've seen, any time PowerShell changes the data type of the object you're returning, and you want the return value to preserve the object's original data type, you can do that by preceding the object being returned with a comma. I first encountered this issue when writing a function that queried a database and returned a DataTable object. The return result was an array of hashtables instead of a DataTable. Changing return $my_datatable_object to return ,$my_datatable_object made the function return an actual DataTable object.

Why don't my functions have common parameters?

I'm trying to write some code that requires examining the functions metadata and processes some other data based on the command's parameters. In the process, I've discovered some really strange behavior that I can't figure out.
I have a function in a psm1 script module, and it's loaded by importing a neighboring psd1 module manifest that declares it as a nested module. I've declared 14 parameters on it explicitly. When I Get-Command and examine the Parameters, I can see it has 23 parameters. 14 are mine; the rest are common parameters.
PS> (Get-Command Install-MyFunctionFromModule).Parameters.Count
23
PS> (Get-Command Install-MyFunctionFromModule).Parameters.keys
MyParameter1
MyParameter2
MyParameter3
MyParameter4
MyParameter5
MyParameter6
MyParameter7
MyParameter8
MyParameter9
MyParameter10
MyParameter11
MyParameter12
MyParameter13
MyParameter14
Verbose
Debug
ErrorAction
WarningAction
ErrorVariable
WarningVariable
OutVariable
OutBuffer
PipelineVariable
(My functions's name does start with Install-. My parameters don't end with numbers, though. These are just dummy placeholders because I didn't want to use the real names.)
This is kind of expected. Functions are supposed to have common parameters.
But in order to test my code that deals with the parameters, I tried creating a test module. Here's what it looks like:
testfun.psm1:
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
function test-noparams() {
Write-Host 'None'
}
function test-differentnamedparams([string]$hello, [switch]$bye) {
Write-Host "names that don't conflict with common params"
}
Then when I import the module, neither functions have any common parameters:
PS> Import-Module .\testfunc.psm1
PS> (Get-Command test-differentnamedparams).Parameters.Count
2
PS> (Get-Command test-differentnamedparams).Parameters | Format-Table -AutoSize
Key Value
--- -----
hello System.Management.Automation.ParameterMetadata
bye System.Management.Automation.ParameterMetadata
PS> (Get-Command test-noparams).Parameters.Count
0
I've tried a few things to see if they made a difference:
Exporting the functions explicitly using Export-ModuleMember
Use a manifest that imports the script module as a NestedModule
Using a script and including it via . .\testfunc.ps1 instead of a module
Defining the function directly in the shell
None of them changed anything.
What determines whether or not a function has common parameters or not? What could cause them not to?
Only advanced functions or scripts support common parameters.
The explicit way to make a script/function an advanced function is to decorate its param(...) block with the [CmdletBinding()] attribute.
However, as jpmc26 himself discovered, use of per-parameter [Parameter()] attributes implicitly makes a script or function an advanced one.
However, note that per-parameter attributes other than [Parameter()] - such as [AllowNull()] or [Alias()] - by themselves do not make an script/function and advanced function.
An easy way to discover whether a given script/function is advanced (supports common parameters) is to pass its name to Get-Help:
# Define a NON-advanced function - no [CmdletBinding()] or [Parameter()] attributes.
PS> function foo { param([string] $bar) 'hi' }; Get-Help foo
NAME
foo
SYNTAX
foo [[-bar] <string>]
ALIASES
None
REMARKS
None
# Define an ADVANCED function EXPLICITLY - note the [CmdletBinding()] attribute
# BEFORE the param(...) block.
PS> function foo { [CmdletBinding()] param([string] $bar) 'hi' }; Get-Help foo
NAME
foo
SYNTAX
foo [[-bar] <string>] [<CommonParameters>]
ALIASES
None
REMARKS
None
# Define an ADVANCED function IMPLICITLY - note the [Parameter(Mandatory)] attribute
# FOR PARAMETER $bar.
PS> function foo { param([Parameter(Mandatory)] [string] $bar) 'hi' }; Get-Help foo
NAME
foo
SYNTAX
foo [[-bar] <string>] [<CommonParameters>]
ALIASES
None
REMARKS
None
Optional reading: pitfall: name collisions with common parameters
As of PSv5.1:
jpmc26 has also discovered that if you accidentally declare a parameter whose name clashes with a common parameter name, you don't get an error when you define the function, but only later when you invoke it, which includes passing it to Get-Help:
# Define advanced function that mistakenly names a a parameter for
# a common parameter:
PS> function foo { [CmdletBinding()] param([string] $Verbose) 'hi' }
# NO error is reported at this point.
# Later invocation of the function, including introspection of its parameters
# when you pass it to Get-Help, then surfaces the problem:
PS> Get-Help foo
Get-Help : A parameter with the name 'Verbose' was defined multiple times for the command.
At line:1 char:1
+ Get-Help foo
+ ~~~~~~~~~~~~
+ CategoryInfo : MetadataError: (:) [Get-Help], MetadataException
+ FullyQualifiedErrorId : ParameterNameAlreadyExistsForCommand,Microsoft.PowerShell.Commands.GetHelpCommand

How to Output value from function to caller but not to console

Say I have this simple PowerShell function:
function testit() {
return $true > $null
}
Write-Host "testing"
$thistest = testit
Write-Host "value = $thistest"
When I use it in my PowerShell script, I want to receive the value in the script but I don't want it to show in the console.
How do I keep the return value in the pipeline but just hide it from console?
If I use the > $null then it suppresses the output completely - I just want it to not show in the console, but I still want the value.
As documented PowerShell functions return all non-captured output to the caller. If the caller doesn't do anything with the returned value PowerShell automatically passes it to Out-Default, which then forwards it to Out-Host (see this article written by Don Jones).
Using redirection operators on the return value inside the function effectively suppresses the return value so that the function wouldn't return anything.
If you have a function like this:
function testit {
return $true
}
and call it by itself:
testit
PowerShell implicitly does this:
testit | Out-Default
which effectively becomes
testit | Out-Host
If you capture the return value in a variable
$thistest = testit
the value gets stored in the variable without anything being displayed on the console.
If you redirect the output or pipe it into Out-Null
testit >$null
testit | Out-Null
the return value is discarded and nothing is displayed on the console.
If you want to prevent PowerShell's default behavior of passing uncaptured output at the end of a pipeline to Out-Host you can do so by overriding Out-Default like this:
filter Out-Default { $_ | Out-Null }
or (as #PetSerAl pointed out in the comments) like this:
filter Out-Default {}
However, beware that this modification disables Out-Default for everything in the current scope until you remove the filter again. If you do for instance a Get-ChildItem while the filter is active nothing will be displayed unless you explicitly write the output to the host console:
Get-ChildItem | Out-Host
You remove the filter like this:
Remove-Item function:Out-Default

powershell piped input to export-csv different from -inputobject

In Powershell (3.0), I get different results when piping an object to Export-CSV than I do if I use the -IncludeObject parameter with the same object.
Example:
$a = Get-Process | select -first 5
$a | Export-CSV -Path '.\one.csv'
Export-CSV -InputObject $a -Path '.\two.csv'
Why are the contents of one.csv and two.csv different?
(And in case it's just me ...)
one.csv =
#TYPE System.Diagnostics.Process
"__NounName","Name","Handles","VM","WS","PM","NPM","Path","Company","CPU","FileVersion","ProductVersion","Description","Product","BasePriority","ExitCode","HasExited","ExitTime","Handle","HandleCount","Id","MachineName","MainWindowHandle","MainWindowTitle","MainModule","MaxWorkingSet","MinWorkingSet","Modules","NonpagedSystemMemorySize","NonpagedSystemMemorySize64","PagedMemorySize","PagedMemorySize64","PagedSystemMemorySize","PagedSystemMemorySize64","PeakPagedMemorySize","PeakPagedMemorySize64","PeakWorkingSet","PeakWorkingSet64","PeakVirtualMemorySize","PeakVirtualMemorySize64","PriorityBoostEnabled","PriorityClass","PrivateMemorySize","PrivateMemorySize64","PrivilegedProcessorTime","ProcessName","ProcessorAffinity","Responding","SessionId","StartInfo","StartTime","SynchronizingObject","Threads","TotalProcessorTime","UserProcessorTime","VirtualMemorySize","VirtualMemorySize64","EnableRaisingEvents","StandardInput","StandardOutput","StandardError","WorkingSet","WorkingSet64","Site","Container"
"Process","AATray","390","156721152","29769728","10678272","27600","C:\Program Files\IBM\ISAM ESSO\AA\AATray.exe","IBM Corporation","42.4166719","8.2.1.1143","8.2.1.1143","AccessAgent Tray Icon: Component of ISAM ESSO AccessAgent","ISAM ESSO AccessAgent","8",,"False",,"4844","390","7784",".","0","","System.Diagnostics.ProcessModule (AATray.exe)","1413120","204800","System.Diagnostics.ProcessModuleCollection","27600","27600","10678272","10678272","257536","257536","63672320","63672320","29806592","29806592","194101248","194101248","True","Normal","10678272","10678272","00:00:32.8070103","AATray","15","True","1","System.Diagnostics.ProcessStartInfo","8/2/2016 7:20:30 AM",,"System.Diagnostics.ProcessThreadCollection","00:00:42.4166719","00:00:09.6096616","156721152","156721152","False",,,,"29769728","29769728",,
"Process","ac.activclient.gui.scagent","547","155099136","22593536","8478720","33184","C:\Program Files\ActivIdentity\ActivClient\ac.activclient.gui.scagent.exe","HID Global Identity Assurance","2.2932147","7,0,5,17","7,0","ActivClient Agent","${release.product.name}","8",,"False",,"3648","547","7872",".","0","","System.Diagnostics.ProcessModule (ac.activclient.gui.scagent.exe)","1413120","204800","System.Diagnostics.ProcessModuleCollection","33184","33184","8478720","8478720","274408","274408","62431232","62431232","22659072","22659072","168542208","168542208","True","Normal","8478720","8478720","00:00:01.8876121","ac.activclient.gui.scagent","15","True","1","System.Diagnostics.ProcessStartInfo","8/2/2016 7:20:30 AM",,"System.Diagnostics.ProcessThreadCollection","00:00:02.2932147","00:00:00.4056026","155099136","155099136","False",,,,"22593536","22593536",,
"Process","accagt","166","508174336","17592320","25407488","26116",,,,,,,,"8",,,,,"166","2480",".","0","",,,,,"26116","26116","25407488","25407488","159440","159440","25657344","25657344","17694720","17694720","509747200","509747200",,,"25407488","25407488",,"accagt",,"True","0","System.Diagnostics.ProcessStartInfo",,,"System.Diagnostics.ProcessThreadCollection",,,"508174336","508174336","False",,,,"17592320","17592320",,
"Process","acevents","506","140414976","22474752","8048640","30856","C:\Program Files\ActivIdentity\ActivClient\acevents.exe","HID Global Identity Assurance","42.6350733","5,0,4,4","5,0","ActivIdentity Event Service","ActivClient Services","8",,"False",,"3872","506","8256",".","0","","System.Diagnostics.ProcessModule (acevents.exe)","1413120","204800","System.Diagnostics.ProcessModuleCollection","30856","30856","8048640","8048640","249632","249632","61378560","61378560","22528000","22528000","157003776","157003776","True","Normal","8048640","8048640","00:00:24.6013577","acevents","15","True","1","System.Diagnostics.ProcessStartInfo","8/2/2016 7:20:34 AM",,"System.Diagnostics.ProcessThreadCollection","00:00:42.6350733","00:00:18.0337156","140414976","140414976","False",,,,"22474752","22474752",,
"Process","acnamagent","395","98971648","14561280","6586368","28296",,,,,,,,"8",,,,,"395","2012",".","0","",,,,,"28296","28296","6586368","6586368","125784","125784","6647808","6647808","14594048","14594048","101597184","101597184",,,"6586368","6586368",,"acnamagent",,"True","0","System.Diagnostics.ProcessStartInfo",,,"System.Diagnostics.ProcessThreadCollection",,,"98971648","98971648","False",,,,"14561280","14561280",,
two.csv =
#TYPE System.Object[]
"Count","Length","LongLength","Rank","SyncRoot","IsReadOnly","IsFixedSize","IsSynchronized"
"5","5","5","1","System.Object[]","False","True","False"
For context, I'm trying to splat my parameters to Export-CSV, but I run into this when I pass -InputObject, and I can't pipe the input and then splat the rest of the parameters.
Thanks.
This is the expected behavior.
When you pipe in through the pipeline, arrays, collections, enumerable stuff, etc. gets processed item by item. This is usually what you want.
When you use -InputObject, it accepts the array as a single object.
The best way to see this is to use Get-Member:
$a = Get-Process | select -first 5
$a | Get-Member
Get-Member -InputObject $a
In the first invocation, you'll see the data type and members of each element. In the second invocation you'll see the type and members of the collection object.
Depending on the cmdlet, you may not notice difference at all because it's handling both cases (see the pipeline function at the end of my answer).
But in the case of Export-Csv, or ConvertTo-Json, or other serialization type cmdlets, you want this difference; otherwise it's very difficult to serialize the array explicitly when you want to.
Another way to demonstrate it:
$sb = {
$_
Write-Verbose $_.Count -Verbose
}
$a | ForEach-Object -Process $sb
ForEach-Object -Process $sb -InputObject $a
When writing your own pipeline functions, a common way to work around the different ways of receiving the object is to use the Process {} block along with foreach:
function Test-Pipeline {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
$MyVal
)
Process {
Write-Verbose $MyVal.Count -Verbose
foreach($v in $MyVal) {
$v
}
}
}
$a | Test-Pipeline
# Process block gets called once for each element
Test-Pipeline -MyVal $a
# Process block gets called once total, with the variable being an array
This works well because foreach doesn't fail if you give it a single non-array object, it just executes once.