I'm having troubles wrapping my head around the code logic needed to get my Param block functioning as I want in PowerShell 5.1. A very simplified (but usable for my request) script with the Param block is here and this works exactly as I want:
[CmdletBinding(DefaultParameterSetName = 'All')]
Param (
[Parameter(Position=0,Mandatory=$True)]
[String]$Name,
[Parameter(ParameterSetName = 'All')]
[Switch]$All,
[Parameter(ParameterSetName = 'Individual')]
[Switch]$P1,
[Parameter(ParameterSetName = 'Individual')]
[Switch]$P2
)
If ($All -Or ($P1.ToBool() + $P2.ToBool() -eq 0)) {
$All = $True
$P1 = $True
$P2 = $True
}
"`$Name is $Name"
"`$All is $All"
"`$P1 is $P1"
"`$P2 is $P2"
The auto-completion of parameters when running this script above works as intended. If I use the "-All" switch, then -P# are not available. If I use -P# switche, -All is not available. If I omit the -Name, then it prompts me to put in a name. If I use the -All switch, I want all of the individual -P# options to later be set to $True. If I use no switches at all, it prompts me for a Name then sets all options to $True.
The first problem is that when using DefaultParameterSetName = 'All' (which I had to do in order to make the script work without any switches on the command line), then the $All variable is NOT actually being set to $True when it is not present on the command line. I had to make the "If" block in order to overcome that behavior. This makes the next problem come up because the actual script I'm trying to use this in will have fifteen or more -P# switches. That will make the "If" test more complex and ugly.
Is there a better way I can do this? Maybe something in the layout of my Parameter Sets? I could even eliminate the "-All" switch entirely if there's an easier way to evaluate that none of the -P# switches are used. Is there an easier way to add up the boolean value of all Parameters named P#? I've stumbled across the $MyInvocation variable and $MyInvocation.MyCommand.Parameters seems promising but I'm not sure exactly how to process that either.
Update after answer found:
Here is my new, simplified working code sample which I arrived at thanks to all the suggestions here. The "-All" Switch was unnecessary. I decided to go with the Get-Variable method here for now due to its simplicity and scalability, but it does require a common prefix on the Switch variables. The If() block will remain the same no matter how many variables are used.
Param (
[Parameter(Position=0,Mandatory=$True)]
[String]$Name,
[Alias('Blue')]
[Switch]$OptionBlue,
[Alias('Red')]
[Switch]$OptionRed,
[Alias('Yellow')]
[Switch]$OptionYellow
)
$AllOptions = Get-Variable -Name 'Option*'
If (-Not $AllOptions.Value.Contains($True)) {
"None of the Option Switches were used, setting all Option Variables to $True"
ForEach ($Option In $AllOptions) {$Option.Value = $True}
}
"`$Name: $Name"
"`$OptionBlue: $OptionBlue"
"`$OptionRed: $OptionRed"
"`$OptionYellow: $OptionYellow"
and here's how it works:
PS C:\Scripts\PowerShell> .\Test-Params.ps1
cmdlet Test-Params.ps1 at command pipeline position 1
Supply values for the following parameters:
Name: Testing
None of the Option Switches were used, setting all Option Variables to True
$Name: Testing
$OptionBlue: True
$OptionRed: True
$OptionYellow: True
PS C:\Scripts\PowerShell> .\Test-Params.ps1 -Name Testing -OptionRed
$Name: Testing
$OptionBlue: False
$OptionRed: True
$OptionYellow: False
PS C:\Scripts\PowerShell> .\Test-Params.ps1 -Name Testing -Blue -Yellow
$Name: Testing
$OptionBlue: True
$OptionRed: False
$OptionYellow: True
PS C:\Scripts\PowerShell> .\Test-Params.ps1 -Yellow -OptionRed
cmdlet Test-Params.ps1 at command pipeline position 1
Supply values for the following parameters:
Name: Testing
$Name: Testing
$OptionBlue: False
$OptionRed: True
$OptionYellow: True
Final Update
I figured out the way to avoid any issue where the script's switch parameters might match an already existing variable in the scope. Pulling the script's parameters that match the proper prefix/suffix used in the script's parameter names using $MyInvocation and then passing those specific names to Get-Variable avoids the issue. The switches also work correctly both when they are not present, or if they are explicitly set to $False. If more switches are needed, simply add them in the Param block with the proper prefix/suffix in the name and an alias for the simpler version. I think this bit of the code is bulletproof now...
Param (
[Parameter(Position=0,Mandatory=$True)]
[String]$Name,
[Alias('Blue')]
[Switch]$OptionBlue,
[Alias('Red')]
[Switch]$OptionRed,
[Alias('Yellow')]
[Switch]$OptionYellow
)
$OptionNotAnOption = $True
$OptionSwitches = ForEach ($Option In ($MyInvocation.MyCommand.Parameters.Keys | Where {$_ -Like "Option*"})) {Get-Variable $Option}
If ($OptionSwitches.Value.IsPresent -NotContains $True) {
"All options are `$False or not present: Enabling all options"
ForEach ($Switch In $OptionSwitches) {
Get-Variable -Name ($Switch.Name) | Set-Variable -Value $True
}
}
"`$Name: $Name"
"`$OptionBlue: $OptionBlue"
"`$OptionRed: $OptionRed"
"`$OptionYellow: $OptionYellow"
"`$OptionNotAnOption: $OptionNotAnOption"
You can use the $PSBoundParameters "automatic variable" to access the parameters specified in the call to the script / function and alter the function's behaviour accordingly.
For example:
function Invoke-MyFunction
{
param
(
[string] $Name,
[switch] $P1,
[switch] $P2
)
# how many $Pn parameters were specified in the call to the function?
$count = #( $PSBoundParameters.GetEnumerator()
| where-object { $_.Key.StartsWith("P") }
).Length;
# if *none* specified then enable *all*
$all = $count -eq 0;
if( $all )
{
$P1 = $true;
$P2 = $true;
}
"`$Name is $Name"
"`$all is $all"
"`$P1 is $P1"
"`$P2 is $P2"
}
And some tests:
PS> Invoke-MyFunction
$Name is
$all is True
$P1 is True
$P2 is True
PS> Invoke-MyFunction -Name "aaa"
$Name is aaa
$all is True
$P1 is True
$P2 is True
PS> Invoke-MyFunction -Name "aaa" -P1
$Name is aaa
$all is False
$P1 is True
$P2 is False
PS> Invoke-MyFunction -Name "aaa" -P2
$Name is aaa
$all is False
$P1 is False
$P2 is True
PS> Invoke-MyFunction -Name "aaa" -P1 -P2
$Name is aaa
$all is False
$P1 is True
$P2 is True
but watch out because the way I've evaluated $count means that specifying -P1:$false or -P2:$false makes $all = $false:
PS> Invoke-MyFunction -Name "aaa" -P1:$false
$Name is aaa
$all is False
$P1 is False
$P2 is False
so you might need to refine the expression to suit whatever you want to happen in this edge case...
Update
If your parameter names don't follow a simple pattern you can do something like this instead:
$names = #( "SomeParam", "AnotherParam", "Param3" );
$count = #( $PSBoundParameters.GetEnumerator()
| where-object { $_.Key -in $names }
).Length;
Assuming those are all switches and they belong to the parameter set of 'Individual', you can set the unset parameters to $true if $All is specified like so:
if ($All.IsPresent)
{
(Get-Command -Name $MyInvocation.MyCommand.Name).Parameters.Values |
Where-Object -FilterScript { $_.ParameterSets.Keys -eq 'Individual' } |
Foreach-Object -Process {
Set-Variable -Name $_.Name -Value $true -PassThru # remove -PassThru to silence the output
}
}
Referencing the current executing command with $MyInvocation.MyCommand.Name, you can pass it to Get-Command for more detailed info. on its parameters. This will allow you to filter by ParameterSets grabbing just the ones falling under Individual. Finally, it will pass it to Set-Variable setting all the switches to $true in a dynamic sense.
Note: I typed this up on my phone so there may be some typos that should be easy to correct.
$cmdlet="Disable-RemoteMailBox"
$arguments = #{Identity=$identity;DomainController=$domaincontroller;Archive=""}
$command_args=""
$arguments.keys | ForEach-Object{
$message = '-{0} {1} ' -f $_, $arguments[$_]
$command_args+= $message
}
$result=& $cmdlet #arguments 2>&1
In the end this is executed:
Disable-RemoteMailBox -Identity abc#corp.com -DomainController dc.corp.local -Archive
but i need to add a confirm:$false
Disable-RemoteMailBox -Identity abc#corp.com -DomainController dc.corp.local -Archive -Confirm:$false
How to add this $false in the Hashtable?
Change the $arguments hashtable from:
$arguments = #{Identity=$identity;DomainController=$domaincontroller;Archive=""}
to
$arguments = #{Identity=$identity;DomainController=$domaincontroller;Archive="";Confirm=$false}
Adding to Mathias's concise answer
Excepting for the confirm functionality's integration with the $ConfirmPreference preference variable, the -Confirm common parameter can be looked at as a simple switch parameter. It is either present or not present. However, PowerShell's internal type conversion engine will evaluate a [Switch] more like a [Boolean] You can see this if you cast a [Bool] to a [Switch].
[Switch]$true or [Switch]$false will return IsPresent True/False respectively.
If you specify Confirm = $false in a splatting hash table, the type coercion (casting) that occurs during the parameter binding will handle it correctly. This is also true for any other switch parameter, even custom ones you define in your custom functions. This type conversion is also noticiable when you need to evaluate a switch parameter internal to a function.
If I specify a switch parameter named $Delete
Param( [Switch]$Delete )
Then internally I can execute logic like:
If( $Delete -eq $true ) {
# Delete the file or whatever...
}
Of course, you can shorten to:
If( $Delete ) {
# Delete the file or whatever...
}
However, you don't need a deep understanding of PowerShell's type conversion system to use Boolean or Switch parameters in splatting hash tables. It's documented in about_Splatting. The first few lines will explain hash table splatting of switch parameters.
In the below sample module file, is there a way to pass the myvar value while importing the module.
For example,
import-module -name .\test.psm1 -?? pass a parameter? e.g value of myvar
#test.psm1
$script:myvar = "hi"
function Show-MyVar {Write-Host $script:myvar}
function Set-MyVar ($Value) {$script:myvar = $Value}
#end test.psm1
(This snippet was copied from another question.)
This worked for me:
You can use the –ArgumentList parameter of the import-module cmdlet to pass arguments when loading a module.
You should use a param block in your module to define your parameters:
param(
[parameter(Position=0,Mandatory=$false)][boolean]$BeQuiet=$true,
[parameter(Position=1,Mandatory=$false)][string]$URL
)
Then call the import-module cmdlet like this:
import-module .\myModule.psm1 -ArgumentList $True,'http://www.microsoft.com'
As may have already noticed, you can only supply values (no names) to –ArgumentList. So you should define you parameters carefully with the position argument.
Reference
The -ArgumentList parameter of Import-Module unfortunately does not accept a [hashtable] or [psobject] or something. A list with fixed postitions is way too static for my liking so I prefer to use a single [hashtable]-argument which has to be "manually dispatched" like this:
param( [parameter(Mandatory=$false)][hashtable]$passedVariables )
# this module uses the following variables that need to be set and passed as [hashtable]:
# BeQuiet, URL, LotsaMore...
$passedVariables.GetEnumerator() |
ForEach-Object { Set-Variable -Name $_.Key -Value $_.Value }
...
The importing module or script does something like this:
...
# variables have been defined at this point
$variablesToPass = #{}
'BeQuiet,URL,LotsaMore' -split ',' |
ForEach-Object { $variablesToPass[$_] = Get-Variable $_ -ValueOnly }
Import-Module TheModule -ArgumentList $variablesToPass
The above code uses the same names in both modules but you could of course easily map the variable names of the importing script arbitrarily to the names that are used in the imported module.
I'm trying to find a way to get all parameter information from a powershell script. Ex script:
function test()
{
Param(
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
foreach ($key in $MyInvocation.BoundParameters.keys)
{
write-host "Parameter: $($key) -> $($MyInvocation.BoundParameters[$key])"
}
}
test -foo "foo!"
I'd like to get the values of $bar and $baz in a dynamic way without knowing the names of the parameters ahead of time.
I've looked through $MyInvocation properties and methods but I don't see anything besides parameters that are set/passed.
Update 1:
I'm close to getting it with:
function test()
{
Param(
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
foreach($var in (get-variable -scope private))
{
write-host "$($var.name) -> $($var.value)"
}
}
test -foo "foo!"
If i could filter out the script parameters vs the default parameters I would be good to go.
Update 2:
The final working solution looks like this:
function test {
param (
[string] $Bar = 'test'
, [string] $Baz
, [string] $Asdf
)
$ParameterList = (Get-Command -Name $MyInvocation.InvocationName).Parameters;
foreach ($key in $ParameterList.keys)
{
$var = Get-Variable -Name $key -ErrorAction SilentlyContinue;
if($var)
{
write-host "$($var.name) > $($var.value)"
}
}
}
test -asdf blah;
Check this solution out. This uses the CmdletBinding() attribute, which provides some additional metadata through the use of the $PSCmdlet built-in variable. You can:
Dynamically retrieve the command's name, using $PSCmdlet
Get a list of the parameter for the command, using Get-Command
Examine the value of each parameter, using the Get-Variable cmdlet
Code:
function test {
[CmdletBinding()]
param (
[string] $Bar = 'test'
, [string] $Baz
, [string] $Asdf
)
# Get the command name
$CommandName = $PSCmdlet.MyInvocation.InvocationName;
# Get the list of parameters for the command
$ParameterList = (Get-Command -Name $CommandName).Parameters;
# Grab each parameter value, using Get-Variable
foreach ($Parameter in $ParameterList) {
Get-Variable -Name $Parameter.Values.Name -ErrorAction SilentlyContinue;
#Get-Variable -Name $ParameterList;
}
}
test -asdf blah;
Output
The output from the command looks like this:
Name Value
---- -----
Bar test
Baz
Asdf blah
To read the value dynamically use the get-variable function / cmdlet
write-host (get-variable "foo")
To print out all of the parameters do the following
foreach ($key in $MyInvocation.BoundParameters.keys)
{
$value = (get-variable $key).Value
write-host "$key -> $value"
}
Hopefully, some may find this one-liner useful:
function test()
{
Param(
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
$MyInvocation.MyCommand.Parameters | Format-Table -AutoSize #{ Label = "Key"; Expression={$_.Key}; }, #{ Label = "Value"; Expression={(Get-Variable -Name $_.Key -EA SilentlyContinue).Value}; }
}
test -foo "foo!"
Result
Keys Value
---- -----
foo foo!
bar
baz baz
I found this most useful for PS4 (Windows 2012 R2) - it includes default values / optional parameters:
$cmdName = $MyInvocation.InvocationName
$paramList = (Get-Command -Name $cmdName).Parameters
foreach ( $key in $paramList.Keys ) {
$value = (Get-Variable $key -ErrorAction SilentlyContinue).Value
if ( $value -or $value -eq 0 ) {
Write-Host "$key -> $value"
}
}
For those of you who do not want to use cmdletbinding() here's a variation on the one liner I found above:
(Get-Command -Name $PSCommandPath).Parameters | Format-Table -AutoSize #{ Label = "Key"; Expression={$_.Key}; }, #{ Label = "Value"; Expression={(Get-Variable -Name $_.Key -EA SilentlyContinue).Value}; }
$PSCommandPath is always available
I played with the 2 solutions i liked in this thread, they both work.
however I needed to produce an error out on missing parameter for a build script
$cmdName = $MyInvocation.InvocationName
$paramList = (Get-Command -Name $cmdName).Parameters
foreach ( $key in $paramList.Keys ) {
$value = (Get-Variable $key -ErrorAction Stop)
#Write-Host $value.Value #remove comment for error checking
if ([string]::IsNullOrEmpty($value.Value)){
$(throw ("$key is a mandatory value please declare with -$key <Required value> " ))
}
}
What if I don't know what arguments or how many will be passed? For example, the function could be called with:
test -foo "value1" -bar "value2" -baz "value3"
and someone else might call it with:
test -variable1 "somevalue" -variable2 "somevalue2" -variable3 "somevalue3" -variable4 "value4"
I looked at $MyInvocation and the arguments come across as UnboundArguments, so theoretically I could "pair up" argument 0 with argument 1, 2 with 3, etc and error out if there's an odd number of UnboundArguments.
Stumbled upon this trying to do something similar and figured out my preferred option.
Function ParamTest {
Param (
$t1 = '1234',
$t2,
[switch]$3
)
$MyInvocation |Add-Member -Name:'Param' -MemberType:'NoteProperty' -Value:(
(Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys)|
ForEach-Object -begin:{$h=#{}} -process:{$h.add($_.Name,$_.Value)} -end:{$h}
))
$MyInvocation.Param
}
Result
Name Value
---- -----
t1 1234
3 False
t2
PSVersionTable
Name Value
---- -----
PSVersion 5.1.19041.1320
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.1320
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
A streamlined solution that:
builds on your own approach now shown at the bottom of the question,
while also including an additional piece of information, namely whether each parameter was bound on invocation, i.e. whether an argument was passed or not, based on its presence in the automatic $PSBoundParameter variable, which is a dictionary containing the bound parameters and their values (arguments).
Note: $PSBoundParameters does not include parameters bound by a default value, only those parameters to which an argument was explicitly passed; for your use case, that is arguably desirable in order to distinguish between a default value and an explicit one, but in scenarios where arguments should be passed through it may be desirable to have default-value parameters included - see GitHub issue #3285.
function test {
param (
[string]$foo,
[string]$bar,
[string]$baz = "baz"
)
foreach ($paramName in $MyInvocation.MyCommand.Parameters.Keys) {
$bound = $PSBoundParameters.ContainsKey($paramName)
[pscustomobject] #{
ParameterName = $paramName
ParameterValue = if ($bound) { $PSBoundParameters[$paramName] }
else { Get-Variable -Scope Local -ErrorAction Ignore -ValueOnly $paramName }
Bound = $bound
}
}
}
test -foo "foo!"
The above yields the following:
ParameterName ParameterValue Bound
------------- -------------- -----
foo foo! True
bar False
baz baz False
Note: This solution also handles dynamic parameters correctly:
Such parameters are reflected in $MyInvocation.MyCommand.Parameters if they situationally apply, but - unlike regular static parameters - are never reflected in scope-local variables. If they apply and are also bound, they and their values are reflected in $PSBoundParameters.
Thus - given that an applicable dynamic parameter may not be bound - the Get-Variable call to look for scope-local variables representing static parameters:
Must explicitly be limited to the current scope with -Scope Local so as not to accidentally pick up unrelated variables of the same name from ancestral scopes for unbound dynamic parameters.
Must ignore errors from the potentially resulting failure to find a variable in the current scope, using -ErrorAction Ignore.
I wanted a compact string of parameter key/value pairs that I can write out when catching error. Use content of the other answers, I came up with this:
$parameters = (Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys) |
Select-Object Name, Value | ForEach-Object { "$($_.Name) : $($_.Value)" }) -join ' | '
Sample output:
ComputerName : SomePC | Directory : C:\Tools\LogExpert | UserName : Hans Wurst | YesOrNo : False
Thanks to all other posters with great & helpful answers above!
I'm cobbling together some of the above posts to try to meet my requirements.
I'd like to show Params in a Powershell-language-compatible format, so that one could easily see & display these params, and then also copy+pasta back into either a script as Param() Defaults (ParamsFormat1) or into a commandline/function call (CommandFormat2). I struggled with this for a while, so I hope this saves someone some time.
Collection of various Scripts from above:
# Note: there are some subtle differences between these versions below
# Specifically in the first line for *which* object you're checking for Parameters, and subsequently what type of Parameters you're looking at.
# PSCmdlet version REQUIRES [CmdletBinding()] on your function!!!
# $($(Get-Command -Name $($PSCmdlet.MyInvocation.InvocationName)).Parameters) | `
# %{ Get-Variable -Name $_.Values.Name -ErrorAction SilentlyContinue; }
# -Scope:'Local' FILTERS out any global params
# (Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys)) | ft
# PSCommandPath supposedly available/usable "in all situations" - so this may be the most useful of all of them, BUT it shows global params [Debug, ErrorAction, etc...]
# (Get-Command -Name $PSCommandPath).Parameters | `
# %{ Get-Variable -Name $_.Values.Name -ErrorAction SilentlyContinue; }
ParamsFormat1 / HYBRID VERSION: To output "Default Params" as you would see them in a function definition. Combines the -Scope:"Local" and "always available" versions to get BOTH param TYPES as well as Name, Value
Write-Host "Params("
# TBD Figure out how to expand #{} of System.Collections.Hashtable
# HYBRID VERSION: Combine the -Scope:"Local" and "always available" versions to get BOTH param TYPES as well as Name, Value
# If you remove LocalList Filter here, it will also show "Global" function properties like Debug, ErrorAction, etc...
$LocalList = $(Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys) | Select-Object -ExpandProperty Name) -join "|"
# VERSION: Wrapper script with DEFAULTS : normally DOES include Global vars [Debug, ErrorAction, etc]. but DOES preserve param order as-written.
((Get-Command -Name $PSCommandPath).Parameters | `
Select -ExpandProperty Values | `
Where-Object { $_.Name -Match $LocalList } | `
Format-Table -HideTableHeaders -AutoSize `
#{ Label="Type"; Expression={"[$($_.ParameterType )]"}; }, `
#{ Label="Name"; Expression={"`t`$$($_.Name)"}; }, `
#{ Label="Equals"; Expression={"="}; }, `
#{ Label="Value"; Expression={ If( $_.ParameterType -Match "String" ) { "`"$((Get-Variable -Name $_.Name -EA SilentlyContinue).Value)`"" } Else{ $((Get-Variable -Name $_.Name -EA SilentlyContinue).Value)}; }; }, `
#{ Label="RowEndComma"; Expression={ "," }; }
##{ Label="Value"; Expression={ $((Get-Variable -Name $_.Name -EA SilentlyContinue).Value) }; } # (OPTIONAL) Values only, no wrapping quotes
)
Write-Host ")";
CommandFormat2 / SIMPLER VERSION: Call with CommandLine Args (TYPES not needed). This filters out Global vars, does NOT preserve param ordering. (sorted alphabetically?)
# VERSION: Call with CommandLine Args (TYPES not available - TBD needs more work to display String, Array, and Hashmap/PsCustomObject ["", #() and {}] types better): filters out Global vars, does NOT preserve param ordering.
# If needed, cut+paste OPTIONAL lines down above Format-Table line.
# Where-Object { $_.Value } | ` # (Optional) remove any Null items.
# Sort-Object -Property Name | ` # (Optional) sort output
(Get-Variable -Scope:'Local' -Include:#($MyInvocation.MyCommand.Parameters.keys) | `
Format-Table -HideTableHeaders -AutoSize `
#{ Label="Name"; Expression={"`t-$($_.Name)"}; }, `
#{ Label="Equals"; Expression ={":"}; }, `
#{ Label="Value"; Expression={ If( $_.ParameterType -NotMatch "String" ) { $_.Value; } Else {"`"$($_.Value)`"";} }; Alignment="left"; }
)
This code:
import-module WebAdministration
Get-WebAppPoolState AppPoolName
Produces the following output:
Value
- -
Stopped
But this code:
import-module WebAdministration
$state = Get-WebAppPoolState AppPoolName
WRITE-HOST $state
Produces this output:
Microsoft.IIs.PowerShell.Framework.CodeProperty
When I get the state of the App Pool using Get-WebAppPoolState, I need a boolean value of some sort to assign to the variable so I can use it in a conditional statement.
I cant use the Microsoft.IIs.PowerShell.Framework.CodeProperty line.
How do I correct this?
Get-WebAppPoolState is not returning a string but an object of type CodeProperty. You'll want the Value property from that object, i.e.:
$state = (Get-WebAppPoolState AppPoolName).Value;
I presume some display converter is kicking in the first case when it gets written to output which is why Stopped is displayed but not for writing to host so you get the default object representation (which is the type name) instead.
Not tested, but does this work better?
$state = $(Get-WebAppPoolState AppPoolName)
Another approach is to use the Select-Object cmdlet with ExpandProperty to get the value of 1 or more properties from an object.
$pool = "app-pool-name"
$state = Get-WebAppPoolState $pool | Select -ExpandProperty Value
Write-Host $state