Does PowerShell splatting support inline hashtable? - powershell

I want to use splatting syntax, but I want to do it inline
So instead of:
$p = #{Path = '.'}
ls #p
I want to do
ls ##{Path = '.'}
but this is a syntax error.
Is there anyway to splat a hashtable without having to write a separate variable?
Why do I want to do this? I prefer the hashtable syntax for defining alot of parameters (like 4 or more). But I'd rather not define a variable, I just want to have the cmdlet I'm calling at the "top" of the hashtable definition.

The splat operator will look for a variable whose name matches the characters after the splat sign, it doesn't try to resolve the characters after the sign as a statement to get the value. You cant do it inline. Link to TechNet.

As Francois mentioned, this is not possible yet, however, there are some discussions taking place around adding expanded splatting functionality (including this feature specifically) in the PowerShell RFC repository on GitHub:
RFC: https://github.com/PowerShell/PowerShell-RFC/blob/master/1-Draft/RFC0002-Generalized-Splatting.md
Discussion: https://github.com/PowerShell/PowerShell-RFC/issues/6

Related

Powershell outputs vertically

I just wanted to run a simple command like echo #profile, but the output is vertical. I can theoretically read and understand the output, but it is a big unconvenience. How can I fix it?
You don't normally reference variables with an # symbol, you almost always use the $ to reference a variable's value by the variable name. You can also use the Variable: provider or Get-Variable, but I won't get into those here.
If you were to omit echo, you would actually get the following error message:
The splatting operator '#' cannot be used to reference variables in an expression.
'#var' can be used only as an argument to a command. To reference variables
in an expression use '$var'.
This is because using #var is a technique called Splatting, which is the practice of using an array or hashtable to provide the arguments to a command, function, or cmdlet. Note you cannot currently splat arguments to methods. Review my answer linked above for more information on how to actually splat arguments in several use cases.
As for why you get the vertical output, note that echo is actually an alias to Write-Output. Write-Output accepts positional arguments, for which it will output each object passed in on its own line. When you splat a string as an array of arguments to a function, it converts the string to an array of characters, so effectively you are passing in each character of #profile to Write-Output as its own argument, then spitting each element of the array back out. And when PowerShell displays an array directly to the console, it displays each element on its own line.
Note: Of the different Write- cmdlets, Write-Output is unique in that it will output each positional parameter on its own line as it returns an array for each argument you pass in. The other Write- cmdlets will instead join each element of the array into a single space-delimited string. The Write- cmdlets come into play when working with the different output streams in PowerShell. Here is another answer of mine which explains what the different output streams mean and how to write to them.
In addition, for displaying purely information text to the end user that does not need additional programmatic processing in your script or session, use Write-Host.
This is why you get the vertical output, because #profile is being converted to an array, then splatted into Write-Output as an array of characters, and Write-Output will write back all arguments of an array as individual elements of a new array. PowerShell will then display the new array with each element on its own line.
I suspect what you actually want to do is output $profile. You can use one of the following techniques (note that echo/Write-Output are often redundant to use):
# Use the alias
echo $profile
# Use Write-Output
Write-Output $profile
# Omit Write-Output entirely
$profile
# View one of the alternative profiles by name
# CurrentUserCurrentHost is the default
# and is most often the one you are looking for
$profile.CurrentUserCurrentHost
$profile.CurrentUserAllHosts
$profile.AllUsersCurrentHost
$profile.AllUsersAllHosts

Powershell splatting: pass ErrorAction = Ignore in hash table

Here's a script to list directories / files passed on the command line -- recursively or not:
param( [switch] $r )
#gci_args = #{
Recurse = $r
ErrorAction = Ignore
}
$args | gci #gci_args
Now this does not work because Ignore is interpreted as a literal. What's the canonical way to pass an ErrorAction?
I see that both "Ignore" and (in PS7) { Ignore } work as values, but neither seems to make a difference for my use case (bad file name created under Linux, which stops PS5 regardless of the ErrorAction, but does not bother PS7 at all). So I'm not even sure the parameter has any effect.
because Ignore is interpreted as a literal
No, Ignore is interpreted as a command to execute, because it is parsed in argument mode (command invocation, like a shell) rather than in expression mode (like a traditional programming language) - see this answer for more information.
While using a [System.Management.Automation.ActionPreference] enumeration value explicitly, as in filimonic's helpful answer, is definitely an option, you can take advantage of the fact that PowerShell automatically converts back and forth between enum values and their symbolic string representations.
Therefore, you can use string 'Ignore' as a more convenient alternative to [System.Management.Automation.ActionPreference]::Ignore:[1]
$gci_args = #{
# ...
ErrorAction = 'Ignore'
}
Note that it is the quoting ('...') that signals to PowerShell that expression-mode parsing should be used, i.e. that the token is a string literal rather than a command.
Also note that -ErrorAction only operates on non-terminating errors (which are the typical kind, however) - see this answer for more information.
As for discovery of the permissible -ErrorAction values:
The conceptual about_CommonParameters help topic covers all common parameters, of which -ErrorAction is one.
Many common parameters have corresponding preference variables (which accept the same values), covered in about_Preference_Variables, which allow you to preset common parameters.
Interactively, you can use tab-completion to see the permissible values (as unquoted symbolic names, which you simply need to wrap in quotes); e.g.:
# Pressing the Tab key repeatedly where indicated
# cycles through the acceptable arguments.
Get-ChildItem -ErrorAction <tab>
[1] Note that using a string does not mean giving up type safety, if the context unambiguously calls for a specific enum type, such as in this case. Validation only happens at runtime either way, given that PowerShell is an interpreted language.
However, it is possible for a PowerShell-aware editor - such as Visual Studio Code with the PowerShell extension - to flag incorrect values at design time. As of version 2020.6.0, however, that does not yet appear to be the case. Fortunately, however, tab-completion and IntelliSense work as expected, so the problem may not arise.
That said, as zett42 points out, in the context of defining a hashtable entry for latter splatting the expected type is not (yet) known, so explicit use of [System.Management.Automation.ActionPreference] does have advantages: (a) IntelliSense in the editor can guide you, and (b) - assuming that Set-StrictMode -Version 2 or higher is in effect - an invalid value will we reported earlier at runtime, namely at the point of assignment, which makes troubleshooting easier. As of PowerShell 7.1, a caveat regarding Set-StrictMode -Version 2 or higher is that you will not be able to use the intrinsic (PowerShell-supplied) .Count property on objects that don't have it type-natively, due to the bug described in GitHub issue #2798.
I think the best way is to use native type.
$ErrorActionPreference.GetType().FullName # System.Management.Automation.ActionPreference
So, use
$gci_args = #{
Recurse = $r
ErrorAction = [System.Management.Automation.ActionPreference]::Ignore
}

Is there an easy way to have Powershell always show the actual value?

These things drive me nuts:
Is there an easy way to have Powershell just show me the empty string and the list with an empty string in it?
For a while I am maintaining a ConvertTo-Expression which converts an (complex) object into a PowerShell expression which eventually can be used to rebuild most objects. It might useful in situations as comparing test results but also to reveal objects. For details see: readme.
Source and installation
The ConvertTo-Expression script can be installed from the PowerShell Gallery:
Install-Script -Name ConvertTo-Expression
As it concerns a standalone script, installation isn't really required. If you don't have administrator rights, you might just download the script (or copy it) to the required location. You might than simply invoke the script using PowerShell dot sourcing:
. .\ConvertTo-Expression.ps1
Example
The following command outputs the same expression as used to build the object:
$Object = [Ordered]#{
'Null' = $Null
'EmptyString' = ''
'EmptyArray' = #()
'SingleItem' = ,''
'EmptyHashtable' = #{}
}
ConvertTo-Expression $Object
Note the comment from #Mathias there's no functional difference between "one string" and "an array of one string", the pipeline consumes 1 string either way. PowerShell is not node which is described here: PowerShell enumerate an array that contains only one inner array. Some objects might be really different than you expect.
See also: Save hash table in PowerShell object notation (PSON)
This is PowerShell, not Node. So it's not JavaScript or JSON. Also, PowerShell is not Bash or CMD any other regular text-based shell. PowerShell works with objects. .NET objects, in particular. And how objects are represented as text is ... quite a matter of taste. How to represent null? Of course: nothing. How to represent an empty string? Nothing, either. An empty array ... you get my point.
All pipeline output is by default send to Out-Default. In general, the way objects are represented can be controlled by format files: about_Format.ps1xml and about_Types.ps1xml. From PowerShell 6 upwards, the default formats are compiled into the source code, but you can extend them. How you do so, depends on your personal taste. Some options were already mentioned ConvertTo-Json "", ConvertTo-Json #("")), but this would be veryyy JSON-specific.
tl;dr Don't care too much about how objects are represented textually. As you see, there are many possible ways to do so, and also some others. Just make sure your scripts are always object-oriented.
You mean like Python's repr() function? A serialization? "Give me a canonical representation of the object that, when property evaluated, will return an object of the type originally passed?" No, that's not possible unless you write it yourself or you serialize it to XML, JSON, or similar. That's what the *-CliXml and ConvertTo-*/ConvertFrom-* commands are for.
On Powershell 7:
PS C:\> ''.Split(',') | ConvertTo-Json -Compress -AsArray
[""]
PS C:\> '1,2,3,,5'.Split(',') | ConvertTo-Json -Compress -AsArray
["1","2","3","","5"]
The closest would be the ToString() common method. However, that's intended for formatting output and typecasting, not canonical representations or serializations. Not every object even has a string representation that can be converted back into that object. The language isn't designed with that in mind. It's based on C#, a compiled language that favors binary serializations. Javascript requires essentially everything to have a string serialization in order to fulfill it's original design. Python, too, has string representations as fundamental to it's design which is why the repr() function is foundational for serialization and introspection and so on.
C# and .Net go the other way. In a .Net application, if you want to specify a literal empty string you're encouraged to use String.Empty ([String]::Empty in Powershell) because it's easier to see that it's explicit. Why would you ever want a compiled application to tell the user how the canonical representation of an object in memory? You see why that's not a useful thing for C# even if it might be for Powershell?

Splatting variable that start with a space

Is there a way to continue using the name that begins with a space and splat that?
So this of course works:
$Splat = #{
name = 'chrome'
fileversioninfo = $true
}
(Get-Process #Splat)[0]
For me it returns:
ProductVersion FileVersion FileName
-------------- ----------- --------
84.0.4147.125 84.0.4147.125 C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
Now, if I change the variable name to ' Chrome File Path and Version ' I get this:
${ Chrome File Path and Version } = #{
name = 'chrome'
fileversioninfo = $true
}
The variable is valid and returns the properties:
Name Value
---- -----
fileversioninfo True
name chrome
But trying to splat it, it thinks I am trying to create a hashtable because of the brackets:
At line:5 char:23
+ (Get-Process #{ Chrome File Path and Version })[0]
+ ~
Missing '=' operator after key in hash literal.
So my question is, anyone aware of a way to splat a variable that requires brackets around it?
I'm well aware a simple
$splat = ${ Chrome File Path and Version }
Would work, but the question isn't for a workaround, just if there's a way to splat variables with a space as the first character.
I've tried escape characters, single/double quotes, subexpressions and piping to drop the name in place but with no documentation on this, I'm pretty sure it's just not supported :/
Also, in case it matters, I am still on version 5.1.19041.1
Splatting is a method of passing a collection of parameter values to a command as a unit, it's supposed to make your commands shorter and easier to read.
Ok PowerShell provides a way to use special caracters in a variable name, but I don't Think it's a good practice, you should know it exists, you should not use it.
The simple fact it uses brakets interfer with the initialisation of hashtables in splatting. So as #Roman Kuzmin propose it's simply not supported
This is like you wish to have an Variablename like this: variable with space = 1; in other languages. This is not supported
Horribleness of the idea aside, splatting in PowerShell is currently quite limited, so an intermediate variable is the best way. There's a RFC draft which would improve splatting here, but its implementation isn't currently planned.

How can I splat a hashtable directly from a class static method?

How can I get the same output as:
$ht = #{Object="Hi there";Foregroundcolor="Green"}
Write-Host #ht
without using a/the variable $ht ?
Don't get me wrong, I know how to use a basic CMDLet.
I have a static method that generates dynamic hashtables.
Look at this simplified example code:
class HashtableGenerator
{
static [hashtable]Answer()
{
return #{Object="Hallo Welt";ForegroundColor="Green"}
}
}
$ht = [HashtableGenerator]::Answer()
Write-Host #ht
This works just fine, but is it possible to get rid of the $ht variable, so the code would look something like this:
Write-Host #([HashtableGenerator]::Answer()) # Doesn't work
I'm pretty sure what you are looking to do is not possible at least at this time. Splatting is specific to hashtable and array variables explicitly. Not return values of functions, methods etc. Technet for splatting sort of supports this
Splatting is a method of passing a collection of parameter values to a command as unit. PowerShell associates each value in the collection with a command parameter. Splatted parameter values are stored in named splatting variables, which look like standard variables, but begin with an At symbol (#) instead of a dollar sign ($). The At symbol tells PowerShell that you are passing a collection of values, instead of a single value.
Using the # outside of that will tell PowerShell to treat the results as an array. IIRC there is a semi related feature request to splat directly from a hashtable definition instead of saving to a variable first.
Related question talking about splatting from a variable property: Splatting a function with an object's property
Passing function parameters using hashtable is just the same way you implicitly specify them. Hashtable key works as parameter name and its corresponding hashtable value uses as parameter value.
Write-Host -Object 'Hi there' -Foregroundcolor 'Green'