Unable to get value from function in Powershell - powershell

I am trying to get the value from the function which contains 2 values, one is pointed from the current method, and another value is null.
What I expect is to print out the $convertAccountEx value from the function, but it gives me nothing. I have tried to give $null value for $convertAccountEx, but nothing changes.
function ConvertTo-Date(){
Param ($accountEx,$convertAccountEx)
if($accountEx.accountExpires -eq 0){
$convertAccountEx = "Never"
}
else{
$convertAccountEx = [DateTime]::FromFileTime($AccountEx.accountExpires)
}
}
$userObjects = $ADSearch.FindAll()
foreach ($user in $userObjects){
$accountEx = $user.Properties.Item("accountExpires")
ConvertTo-Date -accountEx $accountEx.accountExpires -convertAccountEx $convertAccountEx
$convertAccountEx
}

Your code should look like this:
function ConvertTo-Date(){
Param ($accountEx)
if($accountEx.accountExpires -eq 0){
$convertAccountEx = "Never"
}
else{
$convertAccountEx = [DateTime]::FromFileTime($AccountEx.accountExpires)
}
$convertAccountEx
}
$userObjects = $ADSearch.FindAll()
foreach ($user in $userObjects){
$accountEx = $user.Properties.Item("accountExpires")
ConvertTo-Date -accountEx $accountEx.accountExpires
}
The issue you ran into is called scoping. The idea is, that every variable you create, should be exclusive to its running scope. So the var inside your function is - by definition - a different var than outside, even if they share the name. An easy way to go around this in your example is, to just return the value from you function (by calling it).
What I would advise is: Don't just return the value, but append it to the objects you create. Here a (really) simple example:
function Add-Info {
Param( $user )
$user | Add-Member -NotePropertyName NewProperty -NotePropertyValue 'SomeValue'
$user
}

Related

How to iterate through an array of objects in Powershell

I'm doing some basic validation on form fields. What's the correct way to iterate through an array of objects to validate them? When I try the below I get
The property 'BackColor' cannot be found on this object. Verify that the property exists and can be set.
I guess what I'm missing is a way of telling Powershell these are references to other variables, rather than variables themselves.
$MandatoryFields = #(
'txtUsername',
'txtFirst',
'txtLast',
'txtEmail',
'ComboLicense'
)
ForEach ($Field in $MandatoryFields) {
If ([string]::IsNullOrWhitespace($Field.text)) {
$Validation = "failed"
$Field.BackColor = "red"
}
}
EDIT: Okay, what I needed was the actual variables in the array, like so:
$MandatoryFields = #(
$txtUsername,
$txtFirst,
$txtLast,
$txtEmail,
$ComboLicense
)
Try adding your objects to an array like below
$objects = [System.Collections.ArrayList]#()
$myObject = [PSCustomObject]#{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
$objects.add($myObject)
$myObject1= [PSCustomObject]#{
Name = 'Kevin'
Language = 'PowerShell'
State = 'Texas'
}
$objects.add($myObject1)
foreach($obj in $objects){
$obj.firstname
}
I'm assuming your using System.Windows.Forms to create your form. If that's the case when adding controls you should assign a name to the control. Then you can loop through your controls and check if the control name matches your list of mandatory controls and then execute your check.
$MandatoryFields = #(
'txtUsername',
'txtFirst',
'txtLast',
'txtEmail',
'ComboLicense'
)
$Controls = MyForm.controls
ForEach ($c in $Controls) {
ForEach ($Field in $MandatoryFields) {
if ($c.Name -eq $Field) {
If ([string]::IsNullOrWhitespace($c.text)) {
$Validation = "failed"
$c.BackColor = "red"
}
}
}
}

Using a variables string value in variable name

It should work like:
$part = 'able'
$variable = 5
Write-Host $vari$($part)
And this should print "5", since that's the value of $variable.
I want to use this to call a method on several variables that have similar, but not identical names without using a switch-statement. It would be enough if I can call the variable using something similar to:
New-Variable -Name "something"
but for calling the variable, not setting it.
Editing to add a concrete example of what I'm doing:
Switch($SearchType) {
'User'{
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJUsers_ListBox.Items.Add($Item)
}
}
'Computer' {
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJComputers_ListBox.Items.Add($Item)
}
}
'Group' {
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJGroups_ListBox.Items.Add($Item)
}
}
}
I want this to look like:
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJ$($SearchType)s_ListBox.Items.Add($Item)
}
You're looking for Get-Variable -ValueOnly:
Write-Host $(Get-Variable "vari$part" -ValueOnly)
Instead of calling Get-Variable every single time you need to resolve a ListBox reference, you could pre-propulate a hashtable based on the partial names and use that instead:
# Do this once, just before launching the GUI:
$ListBoxTable = #{}
Get-Variable OBJ*_ListBox|%{
$ListBoxTable[($_.Name -replace '^OBJ(.*)_ListBox$','$1')] = $_.Value
}
# Replace your switch with this
foreach($Item in $OBJResults_ListBox.SelectedItems) {
$ListBoxTable[$SearchType].Items.Add($Item)
}

How to differentiate not set parameter from $false, 0, empty string?

I have function that updates object in WMI. I want user to be able to specify in parameters only values that he wants to update. How can I do it?
function UpdateObject ([bool] $b1, [bool] $b2, [int] $n1, [string] $s1)
{
$myObject = GetObjectFromWmi #(...)
#(...)
#This is bad. As it overrides all the properties.
$myObject.b1 = $b1
$myObject.b2 = $b2
$myObject.n1 = $n1
$myObject.s1 = $s1
#This is what I was thinking but don't kwow how to do
if(IsSet($b1)) { $myObject.b1 = $b1 }
if(IsSet($b2)) { $myObject.b2 = $b2 }
if(IsSet($n1)) { $myObject.n1 = $n1 }
if(IsSet($s1)) { $myObject.s1 = $s1 }
#(...) Store myObject in WMI.
}
I tried passing $null as as parameter but it get's automaticly converted to $false for bool, 0 for int and empty string for string
What are your suggestions?
Check $PSBoundParameters to see if it contains a key with the name of your parameter:
if($PSBoundParameters.ContainsKey('b1')) { $myObject.b1 = $b1 }
if($PSBoundParameters.ContainsKey('b2')) { $myObject.b2 = $b2 }
if($PSBoundParameters.ContainsKey('n1')) { $myObject.n1 = $n1 }
if($PSBoundParameters.ContainsKey('s1')) { $myObject.s1 = $s1 }
$PSBoundParameters acts like a hashtable, where the keys are the parameter names, and the values are the parameters' values, but it only contains bound parameters, which means parameters that are explicitly passed. It does not contain parameters filled in with a default value (except for those passed with $PSDefaultParameterValues).
Building on briantist's answer, if you know that all the parameters exist as properties on the target object you can simply loop through the $PSBoundParameters hashtable and add them one by one:
foreach($ParameterName in $PSBoundParameters.Keys){
$myObject.$ParameterName = $PSBoundParameters[$ParameterName]
}
If only some of the input parameters are to be passed as property values, you can still specify the list just once, with:
$PropertyNames = 'b1','b2','n1','s1'
foreach($ParameterName in $PSBoundParameters.Keys |Where-Object {$PropertyNames -contains $_}){
$myObject.$ParameterName = $PSBoundParameters[$ParameterName]
}
To save yourself having to create a parameter for each property you may want to change, consider using a hashtable or other object to pass this information to your function.
For example:
function UpdateObject ([hashtable]$properties){
$myObject = GetObjectFromWmi
foreach($property in $properties.Keys){
# without checking
$myObject.$property = $properties.$property
# with checking (assuming members of the wmiobject have MemberType Property.
if($property -in (($myObject | Get-Member | Where-Object {$_.MemberType -eq "Property"}).Name)){
Write-Output "Updating $property to $($properties.$property)"
$myObject.$property = $properties.$property
}else{
Write-Output "Property $property not recognised"
}
}
}
UpdateObject -properties {"b1" = $true; "b2" = $false}
If you want a [Boolean] parameter that you want the user to specify explicitly or omit (rather than a [Switch] parameter which can be present or not), you can use [Nullable[Boolean]]. Example:
function Test-Boolean {
param(
[Nullable[Boolean]] $Test
)
if ( $Test -ne $null ) {
if ( $Test ) {
"You specified -Test `$true"
}
else {
"You specified -Test `$false"
}
}
else {
"You did not specify -Test"
}
}
In this sample function the $Test variable will be $null (user did not specify the parameter), $true (user specified -Test $true), or $false (user specified -Test $false). If user specifies -Test without a parameter argument, PowerShell will throw an error.
In other words: This gives you a tri-state [Boolean] parameter (missing, explicitly true, or explicitly false). [Switch] only gives you two states (present or explicitly true, and absent or explicitly false).

Check if a string is not NULL or EMPTY

In below code, I need to check if version string is not empty then append its value to the request variable.
if ([string]::IsNullOrEmpty($version))
{
$request += "/" + $version
}
How to check not in if condition?
if (-not ([string]::IsNullOrEmpty($version)))
{
$request += "/" + $version
}
You can also use ! as an alternative to -not.
You don't necessarily have to use the [string]:: prefix. This works in the same way:
if ($version)
{
$request += "/" + $version
}
A variable that is null or empty string evaluates to false.
As in many other programming and scripting languages you can do so by adding ! in front of the condition
if (![string]::IsNullOrEmpty($version))
{
$request += "/" + $version
}
If the variable is a parameter then you could use advanced function parameter binding like below to validate not null or empty:
[CmdletBinding()]
Param (
[parameter(mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Version
)
if (!$variablename)
{
Write-Host "variable is null"
}
I hope this simple answer will resolve the question.
Source
I would define $Version as a string to start with
[string]$Version
and if it's a param you can use the code posted by Samselvaprabu
or if you would rather not present your users with an error you can do something like
while (-not($version)){
$version = Read-Host "Enter the version ya fool!"
}
$request += "/" + $version
You can use the [string]::IsNullOrEmpty($version) method if it is a string.
But, I was looking for a universal way to check nulls (regardless of data type)
in Powershell. Checking for null (or not null) values in
PowerShell is tricky. Using ($value -eq $null) or ($value -ne
$null) does not always work. Neither does if($value). Using them
can even cause problems later on.
Just read this Microsoft article below (IN IT'S ENTIRETY) to get a grasp
of how tricky nulls can be in Powershell.
[https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.1][1]
I wrote these two functions below for checking for null (or not null) values
in PowerShell. I "believe" they should work for any and all values
and data types.
I hope someone finds them helpful.
I am not sure why MS hasn't put something like this into PowerShell natively to
make handling nulls easier (and less dangerous) in PowerShell.
I hope this helps someone.
If anyone knows of an unseen "pitfall" or problem with this method,
please post a comment here so we can know that.
Thanks!
<#
*********************
FUNCTION: ValueIsNull
*********************
Use this function ValueIsNull below for checking for null values
rather using -eq $null or if($value) methods. Those may not work as expected.
See reference below for more details on $null values in PowerShell.
[https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-null?view=powershell-7.1][1]
An if statement with a call to ValueIsNull can be written like this:
if (ValueIsNull($TheValue))
#>
function ValueIsNull {
param($valueToCheck)
# In Powershell when a parameter of a function does not have a data type defined,
# it will create the parameter as a PSObject. It will do this for
# an object, an array, and a base date type (int, string, DateTime, etc.)
# However if the value passed in is $null, then it will still be $null.
# So, using a function to check null gives us the ability to determine if the parameter
# is null or not by checking if the parameter is a PSObject or not.
# This function could be written more efficiently, but intentionally
# putting it in a more readable format.
# Special Note: This cannot tell the difference between a parameter
# that is a true $null and an undeclared variable passed in as the parameter.
# ie - If you type the variable name wrong and pass that in to this function it will see it as a null.
[bool]$returnValue = $True
[bool]$isItAnObject=$True
[string]$ObjectToString = ""
try { $ObjectToString = $valueToCheck.PSObject.ToString() } catch { $isItAnObject = $false }
if ($isItAnObject)
{
$returnValue=$False
}
return $returnValue
}
<#
************************
FUNCTION: ValueIsNotNull
************************
Use this function ValueIsNotNull below for checking values for
being "not-null" rather than using -ne $null or if($value) methods.
Both may not work as expected.
See notes on ValueIsNull function above for more info.
ValueIsNotNull just calls the ValueIsNull function and then reverses
the boolean result. However, having ValueIsNotNull available allows
you to avoid having to use -eq and\or -ne against ValueIsNull results.
You can disregard this function and just use !ValueIsNull($value).
But, it is my preference to have both for easier readability of code.
An if statement with a call to ValueIsNotNull can be written like this:
if (ValueIsNotNull($TheValue))
#>
function ValueIsNotNull {
param($valueToCheck)
[bool]$returnValue = !(ValueIsNull($valueToCheck))
return $returnValue
}
You can use the following list of calls to ValueIsNull to test it out.
$psObject = New-Object PSObject
Add-Member -InputObject $psObject -MemberType NoteProperty -Name customproperty -Value "TestObject"
$valIsNull = ValueIsNull($psObject)
$props = #{
Property1 = 'one'
Property2 = 'two'
Property3 = 'three'
}
$otherPSobject = new-object psobject -Property $props
$valIsNull = ValueIsNull($otherPSobject)
# Now null the object
$otherPSobject = $null
$valIsNull = ValueIsNull($otherPSobject)
# Now an explicit null
$testNullValue = $null
$valIsNull = ValueIsNull($testNullValue)
# Now a variable that is not defined (maybe a type error in variable name)
# This will return a true because the function can't tell the difference
# between a null and an undeclared variable.
$valIsNull = ValueIsNull($valueNotDefine)
[int32]$intValueTyped = 25
$valIsNull = ValueIsNull($intValueTyped)
$intValueLoose = 67
$valIsNull = ValueIsNull($intValueLoose)
$arrayOfIntLooseType = 4,2,6,9,1
$valIsNull = ValueIsNull($arrayOfIntLooseType)
[int32[]]$arrayOfIntStrongType = 1500,2230,3350,4000
$valIsNull = ValueIsNull($arrayOfIntStrongType)
#Now take the same int array variable and null it.
$arrayOfIntStrongType = $null
$valIsNull = ValueIsNull($arrayOfIntStrongType)
$stringValueLoose = "String Loose Type"
$valIsNull = ValueIsNull($stringValueLoose)
[string]$stringValueStrong = "String Strong Type"
$valIsNull = ValueIsNull($stringValueStrong)
$dateTimeArrayLooseValue = #("1/1/2017", "2/1/2017", "3/1/2017").ForEach([datetime])
$valIsNull = ValueIsNull($dateTimeArrayLooseValue)
# Note that this has a $null in the array values. Still returns false correctly.
$stringArrayLooseWithNull = #("String1", "String2", $null, "String3")
$valIsNull = ValueIsNull($stringArrayLooseWithNull)

How to Pass Multiple Objects via the Pipeline Between Two Functions in Powershell

I am attempting to pass a list of objects from one function to another, one by one.
First function: generate a list of users (objects) near expiry;
Second function: send an email to each user (object)
The first function works fine and outputs a group of objects (or so it would seem) and the second function will accept input and email a single user without issue.
Issues arise only when multiple objects are passed from the first function to the second.
Relevant code snippets are below:
The First function creates a custom object for each located user and adds it to an array, which is then outputted in the end block. Below is an extremely simplified snippet of the code with the essential object creation step:
Function 01
{
#param block goes here etc...
Foreach ($user in $users)
{
$userOutput = #()
$userTable = New-Object PSObject -Property #{
name = $User.Name
SamAccountName = $User.SamAccountName
emailAddress = $User.EmailAddress
expired = $user.PasswordExpired
expiryDate = $ExpiryDate.ToShortDateString()
daysTillExpiry = $daysTillExpiry
smtpRecipientAddress = $User.EmailAddress
smtpRecipientName = $User.Name
}
$userOutput += $userTable
}
Write-Output $userOutput
}
I have also tried writing each custom object ($userTable) straight to the console within each iteration of the Foreach (users) loop.
The Second function accepts pipeline input for a number of matching parameters from the first function, e.g:
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string]$smtpRecipientName
The second function also calls a third function designed specifically to send smtp mail and contains no loops, it just takes the current object from the pipeline and deals with it.
I haven't included the full code for either mail function because it is largely irrelevant. I just want to know whether the objects outputted from the first function can be dealt with one-by-one by the second.
At present, the mail function deals with the first object passed to it, and no others.
Update:
This is what I have in mind (but the second function only deals with the last object that was piped in:
Function Test-UserExp
{
$iteration = 0
For ($i=0;$i -le 9;$i++)
{
$iteration ++
$userTable = New-Object PSObject -Property #{
expiryDate = "TestExpDate_$iteration"
daysTillExpiry = "TestDaysTillExpiry_$iteration"
smtpRecipientAddress = "TestSMTPRecipientAddress_$iteration"
smtpRecipientName = "TestSMTPRecipientName_$iteration"
}
$userTable
}
}
Function Test-MailSend
{
Param
(
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$expiryDate,
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$daysTillExpiry,
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$smtpRecipientAddress,
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$smtpRecipientName
)
Write-Host 'Output from Test-MailSend:'
$expiryDate
$daysTillExpiry
$smtpRecipientAddress
$smtpRecipientName
}
First of all: if you want to process objects in a pipeline, one at the time, do not kill experience by collecting all the objects - that's only necessary if you intend to do something about whole collection at some point. If not than just output objects as soon as you get them:
foreach ($user in $users) {
New-Object PSObject -Property #{
name = $User.Name
SamAccountName = $User.SamAccountName
emailAddress = $User.EmailAddress
# ...
}
}
In your case you output whole collection at the end. That's hardly a pipeline experience if you would ask me.
For the second command: if you intend to create parameter for each property, just leave the part 'ValueFromPipeline' out. Otherwise you may end up with whole object converted to string... If you want to take an object as a whole, leave out 'ValueFromPipelineByPropertyName' and specify correct type. And make sure you have process {} wrapped around the code that uses parameters taken from pipeline.
And finally: why would you write a function to send mails? You have Send-MailMessage, so unless you do something this cmdlet doesn't cover, you probably don't need hand-crafted replacement...
In function 1 you want to create the array before the ForEach loop, so you aren't re-creating the array every iteration.
In the param block for the second function, you want to declare the parameter as an array of strings, not just a string.
Finally, when accepting pipeline input for the second function you will need to use the Begin, Process, and End blocks. The part of the function that repeats for each item should be in the Process block.
Here is a short working sample below:
Function fun1{
$users = #(1,2,3)
$userOutput = #()
Foreach ($user in $users){
$userTable = New-Object PSObject -Property #{
emailAddress = "$user#blah.com"
}
$userOutput += $userTable
}
$userOutput
}
Function fun2{
param(
[parameter(ValueFromPipeLine=$true)]
[String[]]$Recipients
)
begin{}
process{
ForEach ($Recipient in $Recipients){
$_
}
}
end{}
}
fun1 | Select emailAddress | fun2
This will give you the output below:
emailAddress
------------
1#blah.com
2#blah.com
3#blah.com
Here is a great breakdown of how the Begin/Process/End blocks work in PowerShell http://technet.microsoft.com/en-us/magazine/hh413265.aspx
function Set-UserExpiry {
1..10 | foreach {
[PSCustomObject]#{
ExpiryDate = "TestExpDate_$_"
DaysTillExpiry = "TestDaysTillExpiry_$_"
SmtpRecipientAddress = "TestSMTPRecipientAddress_$_"
SmtpRecipientName = "TestSMTPRecipientName_$_"
}
}
}
function Test-UserExpiry {
param
(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$ExpiryDate,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$DaysTillExpiry,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$SmtpRecipientAddress,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$SmtpRecipientName
)
process {
Write-Output 'Output from Test-MailSend:'
$expiryDate
$daysTillExpiry
$smtpRecipientAddress
$smtpRecipientName
Write-Output ''
}
}
Set-UserExpiry | Test-UserExpiry