views:

2594

answers:

2

I've just started doing some PowerShell scripting, and I'm running into a problem testing variables for a value. I try to run everything with all warnings enabled, especially while I'm learning, in order to catch dumb mistakes. So, I'm using CTPV3 and setting strict mode on with "set-strictmode -version latest". But I'm running into a road block with checking incoming variables for a value. These variables may or may not already be set.

# all FAIL if $var is undefined under "Set-StrictMode -version latest"
if ( !$var ) { $var = "new-value"; } 
if ( $var -eq $null ) { $var = "new-value"; }

I can't find a way to test if a variable has a value that doesn't cause warnings when the variable is missing unless I turn off strict mode. And I don't want to turn strict mode on and off all over the place just to test the variables. I'm sure I'd forget to turn it back on somewhere and it looks terribly cluttered. That can't be right. What am I missing?

+7  A: 

You're really testing for two things here, existence and value. And the existence test is the one causing the warnings under the strict mode operation. So, separate the tests. Remembering that PowerShell sees variables as just another provider (just like a file or registry provider) and that all PowerShell variables exist as files in the root folder of the drive called 'variable:', it becomes obvious that you can use the same mechanism that you would ordinarily use to test for any other file existence. Hence, use 'test-path':

if (!(test-path variable:\var)) {$var = $null} # test for EXISTENCE & create
if ( !$var ) { $var = "new-value"; }           # test the VALUE

Note that the current strict mode can be changed in child scopes without affecting the parent scope (eg, in script-blocks). So, you could write a script block that encapsulates removing strict mode and setting the variable without affecting the surrounding program's strictness. It's a bit tricky because of variable scoping. Two possibilities I can think of:

#1 - return the value from the script block

$var = & { Set-StrictMode -off; switch( $var ) { $null { "new-value" } default { $var } }}

or #2 - use scope modifiers

& { Set-StrictMode -off; if (!$var) { set-variable -scope 1 var "new-value" }}

Probably the worst part about these are the error-prone, repetitive use of $var (both with and without the leading $). It seems very error prone. So, instead I'd use a subroutine:

function set-Variable-IfMissingOrNull ($name, $value)
{
$isMissingOrNull = !(test-path ('variable:'+$name)) -or ((get-variable $name -value) -eq $null)
if ($isMissingOrNull) { set-variable -scope 1 $name $value }
}
set-alias ?? set-Variable-IfMissingOrNull
#...
## in use, must not have a leading $ or the shell attempts to read the possibly non-existant $var
set-VarIfMissingOrNull var "new-value"
?? varX 1

This last is probably the way I'd script it.

EDIT: After thinking about your question for a bit longer, I came up with a simpler function that more closely matches your coding style. Try this function:

function test-variable
{# return $false if variable:\$name is missing or $null
param( [string]$name )
$isMissingOrNull = (!(test-path ('variable:'+$name)) -or ((get-variable -name $name -value) -eq $null))
return !$isMissingOrNull
}
set-alias ?-var test-variable
if (!(?-var var)) {$var = "default-value"}

Hope that helps.

Roy
Please accept this answer. Roy has done an excellent job of providing you with an answer. He deserves credit for it.
EBGreen
+1  A: 

Firstly I love Roy's answer, complete and succinct. I just wanted to mention that it seems like you're trying to only set a variable if it's been set already. This seems like a job for read-only variables, or constants.

To make a variable read-only, a constant, use

Set-Variable

From get-help Set-Variable -full

-- ReadOnly: Cannot be deleted or changed without the Force parameter.
-- Constant: Cannot be deleted or changed. Constant is valid only when
creating a new variable. You cannot set the Constant option on an
existing variable.
EdgeVB