Check whether a variable is null - powershell

I'd like to check if a variable is null:
function send_null_param ([ref]$mycredentials){
if (! $mycredentials) {
Write-Host 'Got no credentials'
$mycredentials = Get-Credential 'mydomain.com\myuserid'
} else {
Write-Host 'Got credentials'
}
}
$myidentifier = $null
send_null_param ([ref]$myidentifier)
This code is based on:
https://www.thomasmaurer.ch/2010/07/powershell-check-variable-for-null/,
but this does not work.
How can I fix this?
ps. There is something in Stack Overflow for a string being null but not something more generic:
Check if a string is not NULL or EMPTY

Since you're trying to assign $myCredential with Get-Credential in the case of it being absent, then I assume you want your parameter to be a [PSCredential].
In that case, strongly type your parameter, and mark it as mandatory (by the way [ref] is not needed at all :
function Get-MyCredential {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[PSCredential]
$Credential
)
Write-Host "Got credential with username '$($Credential.Username)'"
}
This way, you really don't have to do any checking at all. Making it Mandatory lets PowerShell enforce that for you, and making it a [PSCredential] ensures that the object is a valid [PSCredential] from the start.
The only other case you might want to check for, depending on what you're doing with the credential, is an empty credential.
To do that you can compare it to [PSCredential]::Empty, and you can do it in a validation attribute so it gets done on parameter binding:
function Get-MyCredential {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[PSCredential]
[ValidateScript( {
$_ -ne [PSCredential]::Empty
} )
$Credential
)
Write-Host "Got credential with username '$($Credential.Username)'"
}
You can do other validation in there if you want (checking for a certain username format, like if it needs to be an email address or something). If it's complex it may be better done within the function body, depends on the scenario.
But for the most part you probably don't need additional validation at all.

This works as intended. You are using a [ref] in your parameter. You can think of it as a pointer. And if you pass a variable to a pointer, the pointer will contain the address of the variable. The value doesn't matter.
A [ref] isn't a pointer, but the concept is It is an object of the type 'System.Management.Automation.PSReference'.
An object of the type PSReference saves the actual Value of the object you're referencing under the property 'Value' and when the function is complete it will save the value back to the original variable.
Your code would work if you use the 'Value'-Property of the 'mycredentials' variable in your if-statement:
function send_null_param ([ref]$mycredentials){
if (! $mycredentials.Value) {
Write-host 'Got no credentials'
$mycredentials = Get-Credential 'mydomain.com\myuserid'
}
else {Write-host 'Got credentials'}
}
$myidentifier=$null
send_null_param ([ref]$myidentifier)
I agree with briantist if there is no special reason you shouldn't be using a [ref].

Add the param block to your function and make it mandatory.
Function New-Creds
{
[CmdletBinding()]
[Alias('nc')]
Param
(
[Parameter(Mandatory=$true,
HelpMessage = 'This is a required field. It cannot be blank')]$MyCredentials
)
# Code begins here
$MyCredentials
}
Results
New-Creds -MyCredentials
New-Creds : Missing an argument for parameter 'MyCredentials'. Specify a parameter of type 'System.Object' and try again.
At line:1 char:11
+ New-Creds -MyCredentials
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Creds], ParameterBindingException
+ FullyQualifiedErrorId : MissingArgument,New-Creds
New-Creds
cmdlet New-Creds at command pipeline position 1
Supply values for the following parameters:
(Type !? for Help.)
MyCredentials: !?
This is a required field. It cannot be blank
MyCredentials: SomeCreds
SomeCreds
New-Creds -MyCredentials AnotherCred
AnotherCred

Related

Unable to utilize ErrorMessage property with ValidateSet in Function

I am currently practicing the use of functions in PowerShell and am running into an error. I created the function below to accept DC Super Hero names and return the name of the hero being passed to the function.
function Get-DCHero {
[CmdletBinding()]
param (
[Parameter(Mandatory)][ValidateSet('Batman','Superman','Aquaman','Wonder Woman','Flash',ErrorMessage = "'{0}' is not a DC Super Hero. Please trying one of the following: '{1}'")]
[string]$Name
)
Write-OutPut "$Name is a DC Super hero."
}
As of now the function works properly without the ErrorMessage portion on the ValidateSet. When including the ErrorMessage portion I am receiving the following error:
Get-DCHero -Name
Property 'ErrorMessage' cannot be found for type 'System.Management.Automation.CmdletBindingAttribute'.
At C:\Users\AAP8801\DCSuperHero.ps1:5 char:98
+ ... n','Flash', ErrorMessage = "'{0}' is not a DC Super Hero. Please tryi ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (ErrorMessage = ...llowing: '{1}'":NamedAttributeArgume
ntAst) [], RuntimeException
+ FullyQualifiedErrorId : PropertyNotFoundForType
When a parameter is passed to the function that is not part of the validation set I would like to able to edit the error message being throw. Can anyone tell me why I am unable to do this successfully?
To complement Santiago Squarzon's helpful answer by spelling out the [ValidateScript()] workaround he mentions, which is slightly easier than defining a custom attribute class:
function Get-DCHero {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateScript({
$set = 'Batman','Superman','Aquaman','Wonder Woman','Flash'
if ($_ -in $set) { return $true } # OK
throw "'$_' is not a DC superhero. Please try one of the following: '$($set -join ',')'"
})]
[string]$Name
)
"$Name is a DC superhero."
}
As Mathias explained in comments, the ErrorMessage property is available on the PowerShell SDK v6.2.0 and above.
As a workaround in Windows PowerShell, you could use the ValidateScript Attribute Declaration or you could create your own attribute declaration by inheriting from ValidateEnumeratedArgumentsAttribute, Base Type of the ValidateSet Class.
The following example can help you get started, and if you're interested in learning more, I would recommend you these nice articles from Kevin Marquette:
Powershell: Creating and using custom attributes
Powershell: Creating parameter validators and transforms
Class Definition
using namespace System.Management.Automation
class MyDCHeroSet : ValidateEnumeratedArgumentsAttribute {
[scriptblock] $Set
[string] $ErrorMessage
[void] ValidateElement([object] $Object) {
$ValidValues = & $this.Set
if($Object -notin $ValidValues) {
throw [ValidationMetadataException]::new(
[string]::Format(
$this.ErrorMessage,
$Object, ($ValidValues -join ',')
)
)
}
}
}
Implementation and Testing
function Get-DCHero {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[MyDCHeroSet(
Set = { 'Batman', 'Superman', 'Aquaman', 'Wonder Woman', 'Flash' },
ErrorMessage = "'{0}' is not a DC Super Hero. Please trying one of the following: '{1}'"
)]
[string] $Name
)
Write-Output "$Name is a DC Super hero."
}
Get-DCHero -Name Spiderman

passing $MyInvocation.UnBoundArguments to a nested function in Powershell

I want a function to pass all of it arguments to another function. Is there a better way of doing this? My version doesn't seem to work right however.
function remote {
$fargs = $MyInvocation.UnboundArguments
remote_version1 #fargs
}
function remote_version1 {
param(
[alias("m", "computer")]
[Parameter(Mandatory=$false)] [string]$Machine = "",
[Parameter(Mandatory=$false)] [string]$loc_file = "",
[Parameter(Mandatory=$false)] [string]$rem_file = "",
[Parameter(Mandatory=$false)] [switch]$tmp,
[Parameter(Mandatory=$false)] [switch]$banner,
[Parameter(Mandatory=$false)] [int]$profile = $script:remote_profile,
[Parameter(Mandatory=$false)] [int]$Hop = $script:remote_hop # Second Hop Mode
)
#write-host $MyInvocation.BoundParameters
write-host "loc_file: $loc_file"
write-host "rem_file: ($rem_file)"
}
$common_flags = #{}
$common_flags["Banner"] = $true
$common_flags["Machine"] = "mymachine"
$common_flags["Hop"] = $true
$common_flags["Profile"] = $true
remote #common_flag -loc_file .\file.txt -tmp
Result:
loc_file: .\file.txt
rem_file: True ##<==== Why True? SHould be "" since
## it isn't set when called??
(I can't seem to reproduce the output by snipping the code out of script.. makes me think powershell has some type of pointer bug?
But this is the problem i'm having... $rem_file is set
to a boolean for some reason, when its a string...)
Maybe i'm passing the wrong type into the "splat operator" #, and the orderingof items is random in the hash? but powershell doesn't seem to complain its the wrong object type?
Is there a way to convert a string into a company and execute it instead of using splat?
The automatic $args variable has built-in magic when used for splatting (#args), allowing it to pass named arguments through properly - note that this does not work with any other arrays, whether self-created array or returned from $MyInvocation.UnboundArguments.
Therefore, the following should meet your needs:
function remote {
# #args passes both named and positional (unnamed) arguments through.
remote_version1 #args
}
Note:
If the relaying function (remote) itself had declared parameters (with a param(...) block), you could also relay them with #PSBoundParameters, via automatic $PSBoundParameters variable.
If a relaying function with declared parameters is non-advanced, you may use #PSBoundParameters in addition to passing unbound arguments through with #args, whereas advanced functions by definition prevent passing unbound arguments (in other words: they do not support $args).
UnboundArguments is an Array instead of a Hash. I simply print the Array to convert it to a string and feed it to invoke-expression.
$fargs = $MyInvocation.UnboundArguments
$fargs = $fargs -replace "True", "`$true"
$fargs = $fargs -replace "False", "`$false"
invoke-expression "remote_version1 $fargs"

Pass null datetime parameter in powershell

So I have a powershell script that takes region and datetime as input and internally in the script it calls a stored procedure.
The stored procedure takes two inputs - region and datetimestamp; the datetimestamp is always null. How do I go about parsing it?
function Reset {
param ([string]$region,[nullable[Datetime]]$statustimestamp)
$conn = New-Object System.Data.SqlClient.SqlConnection("Server=SQL,15010; Database='STG';User ID=SVC;Password=password;Integrated Security=FALSE")
$conn.Open()
$cmd = $conn.CreateCommand()
$cmd.CommandText = "dbo.Reset'$region' ,'$statustimestamp'"
$adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
$dataset = New-Object System.Data.DataSet
[void]$adapter.Fill($dataset)
$dataset.tables[0]
$cmd.CommandText
$dataset.Tables[0] | Export-CSV M:\MyReport.csv -encoding UTF8 -NoTypeInformation
Write-Host 'New report M:\MyReport.csv has been successfully generated'
}
I execute it as
Rest -region IN -statustimestamp NULL
and I get the following error
Reset : Cannot process argument transformation on parameter 'statustimestamp'. Cannot convert value "NULL" to type "System.DateTime".
Error: The string was not recognized as a valid DateTime. There is an unknown word starting at index 0.
At line:1 char:59
+ Reset -region AU -statustimestamp NULL
+ ~~~~
+ CategoryInfo : InvalidData: (:) [Reset], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Reset
To complement dee-see's helpful answer:
Note: However your parameters are declared, $PSBoundParameters.ContainsKey('<parameter-name>') inside a script/function tells you whether an argument was explicitly passed to a given parameter (a default value doesn't count); e.g., with the invocation in your question (had it succeeded), $PSBoundParameters.ContainsKey('statustimestamp') would indicate $true.
If you want your parameter value to be $null by omission:
Declare your parameter simply as [Datetime] $statustimestamp and pass no argument to it on invocation; $statustimestamp will then implicitly be $null.
# Declare a parameter in a script block (which works like a function)
# and invoke the script block without an argument for that parameter:
PS> & { param([Datetime] $statustimestamp) $null -eq $statustimestamp }
True # $statustimestamp was $null
If you want to support explicitly passing $null as an argument:
This may be necessary if you declare a mandatory parameter, yet you want to allow $null as an explicit signal that a default value should be used.
Unfortunately, the specifics of the parameter declaration currently depend on whether the data type of the parameter is a reference type (such as [string] or [System.IO.FileInfo]) or a value type (such as [int] or [datetime]).
You can inspect a given type's .IsValueType property to learn whether it is a value type ($true) or a reference type ($false); e.g.: [datetime].IsValueType yields $true).
If the parameter type is a reference type, you can use the [AllowNull()] attribute:
PS> & {
param(
[AllowNull()]
[Parameter(Mandatory)]
[System.IO.FileInfo] $Foo # System.IO.FileInfo is a *reference type*
)
$null -eq $Foo
} -Foo $null
True # $Foo was $null
Unfortunately, the same technique doesn't work with value types such as [DateTime], so your parameter must indeed be typed as [Nullable[DateTime], as in your question:
PS> & {
param(
[Parameter(Mandatory)]
[AllowNull()] # Because the parameter is mandatory, this is *also* needed.
[Nullable[DateTime]] $Foo # System.DateTime is a *value type*
)
$null -eq $Foo
} -Foo $null
True # $Foo was $null
Note: These requirements - needing to pay attention to the difference between value types and reference types and needing to use a [Nullable[T]] type - are obscure and uncharacteristic for PowerShell.
Doing away with these requirements in favor of a unified approach (making it work for value types the way it already does for reference types) is the
subject of this proposal on GitHub.
Null in PowerShell is represented by $null and not NULL, that's why the error message is saying the string NULL cannot be converted to a (nullable) DateTime.
Rest -region IN -statustimestamp $null
You can also omit the -statustimestamp parameter altogether.

Parameter validateset wildcard

Is it possible to make a Paramater validateset work with a wildcard?
I would want on the * places to accept 0-100.
param
(
[Parameter(Mandatory=$True)]
[validateset("6.1.*.*")]
[string]$variable
)
Error message:
Cannot validate argument on parameter 'variable'. The argument "6.1.1.0" does not belong to the set "6.1.." specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.
+ CategoryInfo : InvalidData: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ParameterArgumentValidationError
Since it looks like you're looking to validate a version, you may want to declare the parameter of the type [version] and use the ValidateScript attribute to validate the value rather than using string matching:
function Test-Version {
param(
[ValidateScript({
$_.Major -eq '6' -and
$_.Minor -eq '1' -and
$_.Build -in (0..100) -and
$_.Revision -in (0..100) -or
$(throw 'Wrong Version')
})]
[version]$Version
)
}
No, that's what [ValidatePattern()] is for:
param(
[Parameter(Mandatory=$True)]
[ValidatePattern('6\.1\.\d{1,3}\.\d{1,3}')]
[string]$variable
)
It takes a regular expression as the parameter.
[ValidateSet()] is meant to be used if there is a small, constant set of values. PowerShell also provides autocompletion for these. For example:
[ValidateSet('Windows', 'Mac', 'Linux')
$OperatingSystem
See this article for more parameter validation attributes.

How to convert parameter type into a different object type

I'm doing some scripting in PowerShell, and I was wondering if there's a way to "declare" a parameter "X" the same way parameter "-Credential" is declared, for example in Get-WMIObject cmdlet.
Let me be more specific. The Credential parameter in almost all cmdlets is a PSCredential Object. But, the argument can be either a PSCredential Object or, a String Object with the username.
[CmdletBinding()]
param ([Parameter(Mandatory = $false)]
[System.Management.Automation.PSCredential]
$Credential)
The problem comes when passing a string. Of course, an argument transformation on the parameter cannot be done. Cannot convert the type "System.String" into a type PSCrendential.
Give this a try:
param(
[System.Management.Automation.Credential()]
$Credential=[System.Management.Automation.PSCredential]::Empty
)
As to parameter argument transformation, check this awesome script:
http://poshcode.org/3024
A bit more of info :)
PowerShell has one of these argument transformations included for use with credentials, so whenever you write a script that has a PSCredential parameter, you should decorate it with the CredentialAttribute like this:
param([Parameter(Mandatory = $false)]
[System.Management.Automation.PSCredential]
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty)
That one’s a little confusing because you leave off the “Attribute” part of the attribute’s name (ie: you don’t have to specify [System.Management.Automation.CredentialAttribute()]), so at first glance, it looks like you’re specifying the Credential type twice. Of course, in reality this is another use of parenthesis in PowerShell. To specify an attribute, you use square braces as with types, but with parenthesis in them (even if the attribute doesn’t require any parameters).
http://huddledmasses.org/more-custom-attributes-for-powershell-parameters/
If you declare a function parameter to have type [T], when you invoke the function you can supply any object of type [X] where [T] has a single-parameter constructor that takes type [X].
In other words, if you can construct a [T] from a [String], you can invoke the function with either a [T] or a [String].
This is how I have done it. I declare the parameter like this:
[Parameter(Position=2)]
[object]$Credential
Then at the beginning of the script:
begin {
Write-Verbose -Message "Starting $($myinvocation.mycommand)"
write-verbose -Message "Using volume $($volume.toUpper())"
#convert credential to a PSCredential if a string was passed.
if ( $credential -is [system.management.automation.psCredential]) {
Write-Verbose "Using PSCredential for $($credential.username)"
}
ElseIf ($Credential) {
Write-Verbose "Getting PSCredential for $credential"
$Credential=Get-Credential $credential
}
} #Begin