Why $Null validation failed in powershell function? - powershell

team!
I have validation script for parameter $Data.
It fail when it get $null.
whats wrong?
[CmdletBinding()]
Param (
[Parameter( HelpMessage = "PsObject data." )]
[AllowNull()]
[AllowEmptyCollection()]
[AllowEmptyString()]
[ValidateScript({
if ( ( $_ -eq $null ) -or ( $_ -eq '' ) ){
$true
}
else {
(( !( $_.GetType().IsValueType ) ) -and ( $_ -isnot [string] ))
}
})]
$Data,
...
$UserObject = Show-ColoredTable -Data $null -View 'SamAccountName', 'Name', 'DistinguishedName', 'Enabled' -Title "AD User list" -SelectMessage "Select AD user(s) to disable VPN access: " -AddRowNumbers -AddNewLine -SelectField "SamAccountName"

Most validation attributes are incompatible with [AllowNull()] because the first thing they all check - before your custom validation is invoked - is whether the input object is $null or not.
Move the validation logic inside the function body:
[CmdletBinding()]
Param (
[Parameter( HelpMessage = "PsObject data." )]
[AllowNull()]
[AllowEmptyCollection()]
[AllowEmptyString()]
$Data
)
# validate $Data here before the rest of the script/command

Related

Is it possible to have [Nullable[bool]] in a bool[] in PowerShell

Is it possible to have [Nullable[bool]] in a bool[] in PowerShell? I tried different solution, approaches but fail to get proper $null, $true, $false for a parameter? Also it seems that [cmdletbinding] changes how things works as well.
enum BoolNull {
null = $null
true = $true
false = $false
}
function Test-Array0 {
param (
[bool[]] $thisValue
)
if ($thisValue -eq $null) {
Write-Output 'Null found'
}
}
function Test-Array1 {
param (
[bool[]] $thisValue
)
foreach ($value in $thisValue) {
if ($value -eq $null) {
Write-Output 'Null found'
}
}
}
function Test-Array2 {
[CmdletBinding()]
param (
[bool[]] $thisValue
)
if ($thisValue -eq $null) {
Write-Output 'Null found'
}
}
function Test-Array {
[CmdletBinding()]
param (
[AllowEmptyCollection()] [AllowNull()][ValidateSet($null, $true, $false)] $thisValue
)
if ($thisValue -eq $null) {
Write-Output 'Null found'
}
}
# this works
function Test-Test {
[CmdletBinding()]
param (
[nullable[bool]] $thisValue
)
if ($thisValue -eq $null) {
Write-Output 'Null found'
}
}
function Test-Array5 {
param (
[boolnull[]] $thisValue
)
foreach ($value in $thisValue) {
if ($value -eq 'null') {
Write-Output 'Null found'
}
}
}
Test-Array0 -thisValue $null #this works
Test-Array -thisValue $null # this doesn't work
Test-Array -thisValue $null, $null, $true # this doesn't work
Test-Array1 -thisValue $null
Test-Array2 -thisValue $null # this
Test-Test -thisValue $null # this works
Test-Array5 -thisValue null, true, null # this works but is completely useless
This is a limitation of the bool type. When you strictly-type the parameter, it can only take $true, $false, 0, and 1. In order to achieve what you want, you can use a [ValidateSet] attribute:
[CmdletBinding()]
param(
[Parameter(Position = 0, Mandatory)]
[ValidateSet($null, $true, $false)]
[object] $ThisValue
)
As a side-note, there used to be a bug with powershell (might still be present) where comparing $null on the right side will cause nothing to be returned, causing logic to fall out of the statement, so it's best to compare on the left:
if ($null -eq $ThisValue) {
After testing your example, I was unable to replicate your problem, however:
function Test-Nullable {
[CmdletBinding()]
param(
[nullable[bool]] $Value
)
if ($null -eq $Value) {
'Yes'
} else {
$Value
}
}
and in array format:
function Test-Nullable {
[CmdletBinding()]
param(
[nullable[bool][]] $Value
)
foreach ($bool in $Value) {
if ($null -eq $bool) {
'Yes'
} else {
$bool
}
}
}
Test-Nullable 5, 3, $null, $true, $false, 0
True
True
Yes
True
False
False

Powershell [ValidateSet()] Between Separate Parameter Sets Using Same Parameter Name

OK, so I'm trying to write an advanced function that uses two different parameter set names, one is Default the other is TestAccountsOnly.
Most of this works fine, however, here's my problem:
The output of Get-Help New-SecondaryAccount gives me this in the SYNTAX section:
SYNTAX
New-SecondaryAccount [-Name] <String> [-AccountType] <String> [-Password] <String> [-Description] <String> [-OwnerEmployeeID] <String>
[[-AdditionalDescription]] [<CommonParameters>]
New-SecondaryAccount [-Name] <String> [-AccountType] <String> [-Password] <String> [-CoreOrReserved] <String> [-Description] <String>
[-OwnerEmployeeID] <String> [[-AdditionalDescription]] [<CommonParameters>]
From the looks of it, this is exactly what I want - one parameter set where I can validate a list of a handful of different -AccountTypes and move along where I have passwords, descriptions, etc., and the other where I validate just one value for AccountType and have a CoreOrReserve parameter that only belongs to the TestAccountsOnly parameter set.
Unfortunately, when trying to test this in the ISE, if I type:
New-SecondaryAccount -Name mehSomeAccount -AccountType, the only suggestion I get from IntelliSense is Test.
Can you not use [ValidateSet()] the way I'm trying to, or am I just doing it wrong?
Would really appreciate it if someone could point this out!
Function New-SecondaryAccount(DefaultParameterSetName="Default")
{
<#
.Synopsis
Creates a new secondary account based on the parameters
.DESCRIPTION
Creates a secondary AD user account based on parameters
specified. This includes several different types of accounts,
and determines the employeeType, OU, and description values
of the account created.
The CoreOrReserved parameter can only be used for accounts
where AccountType is set to Test
.INPUTS
[String]
.OUTPUTS
[ADObject]
.NOTES
.COMPONENT
MyModule_Part1
.FUNCTIONALITY
Active Directory Things
#>
[cmdletBinding(DefaultParameterSetName="Default")]
param(
[Parameter(Mandatory=$True,
Position=0,
ParameterSetName="Default",
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$True,
Position=0,
ParameterSetName="TestAccountsOnly",
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[String]$Name,
[Parameter(Mandatory=$True,
Position=1,
ParameterSetName="Default")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateSet('ADAdmin','ServerAdmin','ServiceAccount','ChuckNorris')]
[Parameter(Mandatory=$True,
Position=1,
ParameterSetName="TestAccountsOnly")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateSet("Test")]
[String]$AccountType,
[Parameter(Mandatory=$True,
Position=2,
ParameterSetName="Default")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{
if($_.Length -ge 12)
{
$True
}
else
{
throw "Password must be at least 12 characters"
$False
}
})]
[Parameter(Mandatory=$True,
Position=3,
ParameterSetName="TestAccountsOnly")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{
if($_.Length -ge 12)
{
$True
}
else
{
throw "Password must be at least 12 characters"
$False
}
})]
[String]$Password,
[Parameter(Mandatory=$True,
Position=2,
ParameterSetName="TestAccountsOnly")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateSet("Core","Reserved")]
[String]$CoreOrReserved,
[Parameter(Mandatory=$True,
Position=3,
ParameterSetName="Default")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{
if($_ -match "^TASK\d{7}\b")
{
$True
}
else
{
throw "Description must be a TASK number only`nEx. TASK1234567"
$False
}
})]
[Parameter(Mandatory=$True,
Position=4,
ParameterSetName="TestAccountsOnly")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{
if($_ -match "^TASK\d{7}\b")
{
$True
}
else
{
throw "Description must be a TASK number only`nEx. TASK1234567"
$False
}
})]
[String]$Description,
[Parameter(Mandatory=$True,
Position=4,
ParameterSetName="Default")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{
if($(Get-ADUser -Filter {EmployeeID -eq $_ -and EmployeeType -eq "E"}) -ne $NULL)
{
$True
}
else
{
throw "$_ must correspond to a valid FTE user's employeeID number"
$False
}
})]
[Parameter(Mandatory=$True,
Position=5,
ParameterSetName="TestAccountsOnly")]
[ValidateNotNull()]
[ValidateNotNullOrEmpty()]
[ValidateScript(
{
if($(Get-ADUser -Filter {EmployeeID -eq $_ -and EmployeeType -eq "E"}) -ne $NULL)
{
$True
}
else
{
throw "$_ must correspond to a valid FTE user's employeeID number"
$False
}
})]
[String]$OwnerEmployeeID,
[Parameter(Mandatory=$False,
ParameterSetName="Default",
Position=5)]
[Parameter(Mandatory=$False,
ParameterSetName="TestAccountsOnly",
Position=6)]
[Switch]$AdditionalDescription
)
BEGIN{}
PROCESS{# implementation doing all the things here}
END{}
Unfortunately, you cannot declare more than one validate set attribute per parameter, which is one reason why its designation is separate.
You might be able to play around with dynamic parameters to get what you want. I stripped out a lot of stuff for clarity.
function New-SecondaryAccount() {
[cmdletBinding()]
param (
[Parameter(Mandatory,
Position = 0,
ValueFromPipeline,
ValueFromPipelineByPropertyName)]
[string] $Name,
[Parameter(Mandatory, Position = 1)]
[string] $Password,
[Parameter(Position = 2)]
[switch] $TestAccount
)
DynamicParam {
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Mandatory = $true
$collection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$collection.Add($attribute)
if ($TestAccount) {
$validationSet = #("Test")
} else {
$validationSet = #("ADAdmin", "ServerAdmin", "ServiceAccount", "ChuckNorris")
}
$collection.Add((New-Object System.Management.Automation.ValidateSetAttribute($validationSet)))
$param = New-Object System.Management.Automation.RuntimeDefinedParameter('AccountType', [string], $collection)
$dictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$dictionary.Add('AccountType', $param)
return $dictionary
}
PROCESS {
<# implementation doing all the things here #>
}
}

Conditional Mandatory in PowerShell

I'm trying to make a parameter mandatory, but only if another parameter uses certain ValidateSet values. It seems that using a code block on Mandatory doesn't work as expected.
function Test-Me {
[CmdletBinding()]
Param (
[Parameter()]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory = {-not ($Type -eq "NoNameRequired")})]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory? = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Set-StrictMode -Version Latest
function Test-Me {
[CmdletBinding(DefaultParameterSetName = "Gorgonzola")]
Param (
[Parameter(Mandatory)]
[int]
$Number,
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[ValidateSet("NameRequired", "AlsoRequired")]
[string]
$Type = "NoNameRequired",
[Parameter(Mandatory, ParameterSetName = "NameNeeded")]
[string]
$Name
)
Process {
Write-Host "I ran the process block."
Write-Host "Number = '$Number'"
Write-Host "Type = '$Type'"
Write-Host "Name = '$Name'"
Write-Host "Name Parameter Mandatory = '$(-not ($Type -eq "NoNameRequired"))'"
}
}
Parameter sets seem to help simulate conditional mandatory parameters.
I can make it to where if either the Type or Name parameter is given, then they are both required. This can happen regardless of other parameters in the function, such as the sibling Number parameter above.
I set the default parameter set name to something random; I usually specify "None". That parameter set name doesn't need to actually exist, again indicated by the Number parameter.
All of this works regardless of your strict mode setting.

Powershell gwmi for other users

I am trying to collect user profile information for users on a machine and I was wondering if I could get it with gwmi. Here is how I get printers for the current user:Get-WmiObject win32_printer. How can I get the same info for the user "Test" on the same machine?
As it happens, I can't sleep, so I came up with these 2 functions:
function Get-UserSid {
[CmdletBinding()]
param(
[Parameter(
ParameterSetName='NTAccount',
Mandatory=$true,
ValueFromPipeline=$true,
Position=0
)]
[System.Security.Principal.NTAccount]
$Identity ,
[Parameter(
ParameterSetName='DomainAndUser',
Mandatory=$true
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[^\\]+$')]
[String]
$Domain ,
[Parameter(
ParameterSetName='DomainAndUser',
Mandatory=$true
)]
[ValidateNotNullOrEmpty()]
[ValidatePattern('^[^\\]+$')]
[String]
$User
)
Begin {
if ($PSCmdlet.ParameterSetName -eq 'DomainAndUser') {
$Identity = New-Object System.Security.Principal.NTAccount -ArgumentList $Domain,$User
}
}
Process {
$Identity.Translate([System.Security.Principal.SecurityIdentifier])
}
}
function Get-PrinterNameByUser {
[CmdletBinding(DefaultParameterSetName='Ambiguous')]
param(
[Parameter(
ParameterSetName='ByAccount',
Mandatory=$true
)]
[System.Security.Principal.NTAccount]
$Account ,
[Parameter(
ParameterSetName='BySID',
Mandatory=$true
)]
[System.Security.Principal.SecurityIdentifier]
$SID ,
[Parameter(
ParameterSetName='Ambiguous',
Mandatory=$true,
Position=0,
ValueFromPipeline=$true
)]
[ValidateNotNullOrEmpty()]
[String]
$Identity
)
Begin {
Write-Verbose "Parameter Set Name: $($PSCmdlet.ParameterSetName)"
if ($PSCmdlet.ParameterSetName -eq 'ByAccount') {
$SID = $Account | Get-UserSid
}
}
Process {
if ($PSCmdlet.ParameterSetName -eq 'Ambiguous') {
try {
$SID = [System.Security.Principal.SecurityIdentifier]$Identity
} catch [System.InvalidCastException] {
$Account = [System.Security.Principal.NTAccount]$Identity
$SID = $Account | Get-UserSid
}
}
Get-ChildItem -Path "Registry::\HKEY_Users\$($SID.Value)\Printers" | Select-Object -ExpandProperty Property -Unique
}
}
Usage
Get-PrinterNameByUser Test
Get-PrinterNameByUser 'domain\test'
Get-PrinterNameByUser 'S-1-S-21-65454546-516413534-4444'
All of those could be piped as well:
'Test' | Get-PrinterNameByUser
'domain\test' | Get-PrinterNameByUser
'S-1-S-21-65454546-516413534-4444' | Get-PrinterNameByUser
'S-1-S-21-65454546-516413534-4444','user1','machine\user2','domain\user3' | Get-PrinterNameByUser
Explanation
In the registry at HKU\S-ID-HERE\Printers there are some keys with properties. The property names are the printers. I wasn't able to test this on enough machines, so I wasn't certain which key(s) I should check, and whether they would be different depending on whether it was a local or network printer, etc., so I'm just getting the properties from all the keys and returning the unique ones.
The helper function Get-UserSid just provides a convenient way to get a SID from a user name.
Most of Get-PrinterNameByUser is just code to figure out what you've given it and translate it at needed. The meat of it that returns what you want is just the one line:
Get-ChildItem -Path "Registry::\HKEY_Users\$($SID.Value)\Printers" | Select-Object -ExpandProperty Property -Unique

the variables can not be set by a function by dot souring

I want to implement a "Log" library in common.ps1 and then use dot souring to load it.
but it does not work as I expected I thought I can get the different value after calling SetLogConfiguratoion, but the value is not changed. then the "Log" function does not work becasue the log path is $null.
Do I miss-understand dot-sourcing?
write-host $g_nodeName ==> show $null
. 'C:\Test\Common.ps1'
write-host $g_nodeName ==> show "unkown"
SetLogConfiguratoion $sqlInstance (join-path $BackupShare 'RemvoeAgent.log')
write-host $g_nodeName ==> still "unkown"
Log 'ERROR' 'Test'
and Common.ps1 is as below
$g_logPath = $null
$g_nodeName = "Unknown"
function SetLogConfiguratoion
{
param
(
[Parameter(
Mandatory=$true,
HelpMessage='NodeName')]
[ValidateNotNullOrEmpty()]
[string]$NodeName,
[Parameter(
Mandatory=$true,
HelpMessage='LogPath')]
[ValidateNotNullOrEmpty()]
[string]$LogPath
)
if($LogPath.StartsWith('Microsoft.PowerShell.Core\FileSystem::'))
{
$g_logPath = $LogPath;
}
else
{
$g_logPath = 'Microsoft.PowerShell.Core\FileSystem::' + $LogPath;
}
$g_NodeName = $NodeName;
}
function Log
{
param
(
[Parameter(
Mandatory=$true,
HelpMessage='Log level')]
[ValidateNotNullOrEmpty()]
[ValidateSet(
'Error',
'Warning',
'Info',
'Verbose'
)]
[string]$level,
[Parameter(
Mandatory=$true,
HelpMessage='message')]
[ValidateNotNull()]
[string]$message
)
if($g_logPath -eq $null)
{
return
}
$time = Get-Date –format ‘yyyy/MM/dd HH:mm:ss’
$msg = "$time :: $level :: $nodeName :: $message"
Add-content $LogPath -value $message + '\n'
}
Dot sourcing the script will make it run in the local scope, and create the functions there, but you're still invoking the function in it's own scope. It's going to set $g_nodename in that scope. If you want all of that to run in the local scope, you need to dot source the script into the local scope to create the functions, then call the functions in the local scope (by preceding them with '. ' (note the space after the dot - that has to be there).
. SetLogConfiguratoion $sqlInstance (join-path $BackupShare 'RemvoeAgent.log')