How to iterate through an array of objects in Powershell - 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"
}
}
}
}

Related

PowerShell: How can I pass a nested hash table to my function that accepts 3 parameters?

I created this function but I don't know how to make it work. here is the code:
function ProgramRegistry {
param (
[Parameter(Mandatory=$false)][HashTable]$HashTable,
[Parameter(Mandatory=$false)][String]$AlertPath,
[Parameter(Mandatory=$false)][String]$AlertName,
[Parameter(Mandatory=$false)][String]$AlertValue
)
foreach ($AlertPath in $HashTable.Values){
foreach($AlertName in $HashTable.Values){
foreach($AlertValue in $HashTable.Values){
New-Item -Path $AlertPath -Force | Out-Null
New-ItemProperty -Path $AlertPath -Name $AlertName -Value $AlertValue -PropertyType DWORD -Force
}
}
}
}
$keys = [ordered]#{
key1 = #{
AlertPath = 'Path'
AlertName = 'Name'
AlertValue = 'Value'
}
key2 = #{
AlertPath = 'Path'
AlertName = 'Name'
AlertValue = 'Value'
}
# and so on...
}
ModifyRegistry #keys
ModifyRegistry -AlertPath "path" -AlertName "name" -AlertValue "value"
I want to be able to call the function in 2 different ways (as shown in the script)
either by defining its 3 parameters explicitly in one line.
or by passing a nested hash table consisting of multiple objects each having the function's 3 parameters.
how can I achieve that?
I want to only modify the function and not the way I call it. I need to call it a bunch of times and want to keep the code for doing it as minimal as possible, like this ModifyRegistry #keys . it's okay if the function itself is complicated and long but I want calls to function to take very little code like that. instead of nested hash table, I could just call the function repeatedly but it'd be too much repeated code and that's what I want to avoid.
You may modify your function to accept a hashtable of hashtables. You just need to provide some logic to check if the hashtable received is a hashtable containing other hashtables that have the values you need or if it is instead a single hashtable containing the values you need. Also needed is to handle the other parameters still when not providing a hashtable. The example below shows how I would do this in an advanced function utilizing a begin, process, and end block. In the begin block we only need to create the collection object that we will use to sift out the inputs. The process block is repeated for each input object received when using the pipeline. If supplying the arguments to the function directly this process block will only run once. We will use this process block to determine and add our input objects to the $inputs arraylist we created. In the end block we will perform the actual processing on each of the objects that we've collected.
function ProgramRegistry {
[cmdletbinding()]
param (
# ValueFromPipeline attribute will allow piping hashtables to the function
[Parameter(Mandatory = $false, ValueFromPipeline)][HashTable]$HashTable,
[Parameter(Mandatory = $false)][String]$AlertPath,
[Parameter(Mandatory = $false)][String]$AlertName,
[Parameter(Mandatory = $false)][String]$AlertValue
)
begin {
# Create an arraylist to collect hashtables for later processing in end block
$inputs = [System.Collections.ArrayList]::new()
}
process {
if ($HashTable) {
if ($HashTable.ContainsKey('AlertPath')) {
# if single hashtable is received with 'AlertPath' key add to inputs for processing in end block
$inputs.Add($HashTable) | Out-Null
}
else {
foreach ($value in $HashTable.Values) {
# check if value of key is a hashtable
if ($value -is [hashtable]) {
# check if hashtable contains key "AlertPath" and if so add to $inputs for processing in end block
if ($value.ContainsKey('AlertPath')) {
$inputs.Add($value) | Out-Null
}
else {
Write-Warning "Invalid hashtable format - missing 'AlertPath' key"
}
} else {
Write-Warning "Object is not a hashtable"
}
}
}
}
else {
# process when not a hashtable by creating a hashtable and adding to $inputs
$inputs.Add(#{
AlertPath = $AlertPath
AlertName = $AlertName
AlertValue = $AlertValue
}) | Out-Null
}
}
end {
# Process hashtables collected in $inputs
foreach ($hash in $inputs) {
# your code here
[pscustomobject]#{
Path = $hash.AlertPath
Name = $hash.AlertName
Value = $hash.AlertValue
}
}
}
}
$keys = [ordered]#{
key1 = #{
AlertPath = 'Path1'
AlertName = 'Name1'
AlertValue = 'Value1'
}
key2 = #{
AlertPath = 'Path2'
AlertName = 'Name2'
AlertValue = 'Value2'
}
# and so on...
}
ProgramRegistry -HashTable $keys
# or
$keys | ProgramRegistry
# or even
ProgramRegistry -HashTable $keys.key1 #or $keys.key1 | ProgramRegistry
If pipeline and advanced function is not wanted you can still do something similar without begin, process, and end blocks. I use nested function 'processit' so that I don't have to repeat the processing logic multiple times
function ProgramRegistry {
param (
[Parameter(Mandatory = $false)][HashTable]$HashTable,
[Parameter(Mandatory = $false)][String]$AlertPath,
[Parameter(Mandatory = $false)][String]$AlertName,
[Parameter(Mandatory = $false)][String]$AlertValue
)
# create nested function to process each hash table.
function processit {
param([hashtable]$hash)
# add processing logic here
[pscustomobject]#{
Path = $hash.AlertPath
Name = $hash.AlertName
Value = $hash.AlertValue
}
}
if ($HashTable) {
if ($HashTable.ContainsKey('AlertPath')) {
# if single hashtable is received with 'AlertPath' key process it
processit -hash $HashTable
}
else {
foreach ($value in $HashTable.Values) {
# check if value of key is a hashtable
if ($value -is [hashtable]) {
# check if hashtable contains key "AlertPath" and if so process it
if ($value.ContainsKey('AlertPath')) {
processit -hash $value
}
else {
Write-Warning "Invalid hashtable format - missing 'AlertPath' key"
}
}
else {
Write-Warning 'Object is not a hashtable'
}
}
}
}
else {
processit #{AlertPath = $AlertPath; AlertName = $AlertName; AlertValue = $AlertValue }
}
}
Update in response to your question regarding using the key name as AlertName
function ProgramRegistry {
param (
[Parameter(Mandatory = $false)][HashTable]$HashTable,
[Parameter(Mandatory = $false)][String]$AlertPath,
[Parameter(Mandatory = $false)][String]$AlertName,
[Parameter(Mandatory = $false)][String]$AlertValue
)
# create nested function to process each hash table.
function processit {
param([hashtable]$hash)
# add processing logic here
[pscustomobject]#{
Path = $hash.AlertPath
Name = $hash.AlertName
Value = $hash.AlertValue
}
}
if ($HashTable) {
if ($HashTable.ContainsKey('AlertPath')) {
# if single hashtable is received with 'AlertPath' key process it
processit -hash $HashTable
}
else {
foreach ($item in $HashTable.GetEnumerator()) {
if ($item.Value -is [hashtable]) {
# check if the hashtable has AlertPath and AlertValue keys
if ($item.Value.ContainsKey('AlertPath') -and $item.Value.ContainsKey('AlertValue')) {
$hash = $item.Value
# check if hashtable contains an AlertName key.
if (-not $hash.ContainsKey('AlertName')){
# If not use parent key name
$hash.AlertName = $item.Key
}
processit -hash $hash
}
else {
Write-Warning "Invalid hashtable format - missing AlertPath and/or AlertValue key"
}
}
else {
Write-Warning "Item does not contain a hashtable"
}
}
}
}
else {
processit #{AlertPath = $AlertPath; AlertName = $AlertName; AlertValue = $AlertValue }
}
}
Calling the function
$items = #{
Alert1 = #{
AlertPath = 'Path1'
AlertValue = 'Value1'
}
Alert2 = #{
AlertPath = 'Path2'
AlertValue = 'Value2'
}
Alert3 = #{
AlertName = 'Overridden AlertName'
AlertPath = 'Path3'
AlertValue = 'Value3'
}
Alert4 = #{
AlertValue = 'Value2'
}
Alert5 = "just a string"
}
ProgramRegistry -HashTable $items
Output
WARNING: Item does not contain a hashtable
WARNING: Invalid hashtable format - missing AlertPath and/or AlertValue key
Path Name Value
---- ---- -----
Path3 Overridden AlertName Value3
Path2 Alert2 Value2
Path1 Alert1 Value1

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).

PowerShell nested property iteration

Looking for some input on the scripts below. Essentially I'm retrieving some json data with Invoke-WebRequest. I needed to export many of the properties to CSV. The data returned from Invoke-WebRequest is contained in an array: $devicedetails. This script works for outputting to CSV.
& {
Foreach ($PC in $DeviceDetails)
{
Foreach ($AppName in $PC.categories)
{
ForEach ($App in $AppName.apps)
{
ForEach($Status in $App.health_status)
{
[pscustomobject] #{
"Device ID" = $PC.device_id
"Device Type" = $PC.device_type
"Device Name" = $PC.device_name
Nickname = $PC.nick_name
Last_Seen = $PC.last_seen
Compliance_Category_Status = $Appname.issue
Compliance_Category = $Appname.category_id
Product_Name = $App.name
Product_Vendor = $App.vendor
Product_Version = $App.version
Product_Health_Item = $Status.status
Product_Health_Status = $Status.issue
}
}
}
}
}
} | Export-CSV -PAth $Outfile -NoTypeInformation
Curious if this is the best way to output properties to CSV. Additionally, I now have the need to do some additional processes on the custom object I'm creating but if I assign a variable to that custom object as shown below, it takes several minutes to complete whereas just exporting to CSV takes 12-13 seconds. Why is the performance so bad?
$DeviceOutput= #()
Foreach ($PC in $DeviceDetails)
{
Foreach ($AppName in $PC.categories)
{
ForEach ($App in $AppName.apps)
{
ForEach($Status in $App.health_status)
{
$DeviceOutput += [pscustomobject] #{
"Device ID" = $PC.device_id
"Device Type" = $PC.device_type
"Device Name" = $PC.device_name
"Nickname" = $PC.nick_name
Compliance_Category_Status = $Appname.issue
Compliance_Category = $Appname.category_id
Product_Name = $App.name
Product_Vendor = $App.vendor
Product_Version = $App.version
Product_Health_Item = $Status.status
Product_Health_Status = $Status.issue
}
}
}
}
}

Unable to get value from function in 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
}

Foreach -parallel object

Recently we started working on scripts that take a very long time to complete. So we dug into PowerShell workflows. After reading some documentation I understand the basics. However, I can't seem to find a way to create a [PSCustomObject] for each individual item within a foreach -parallel statement.
Some code to explain:
Workflow Test-Fruit {
foreach -parallel ($I in (0..1)) {
# Create a custom hashtable for this specific object
$Result = [Ordered]#{
Name = $I
Taste = 'Good'
Price = 'Cheap'
}
Parallel {
Sequence {
# Add a custom entry to the hashtable
$Result += #{'Color' = 'Green'}
}
Sequence {
# Add a custom entry to the hashtable
$Result += #{'Fruit' = 'Kiwi'}
}
}
# Generate a PSCustomObject to work with later on
[PSCustomObject]$Result
}
}
Test-Fruit
The part where it goes wrong is in adding a value to the $Result hashtable from within the Sequence block. Even when trying the following, it still fails:
$WORKFLOW:Result += #{'Fruit' = 'Kiwi'}
Okay here you go, tried and tested:
Workflow Test-Fruit {
foreach -parallel ($I in (0..1)) {
# Create a custom hashtable for this specific object
$WORKFLOW:Result = [Ordered]#{
Name = $I
Taste = 'Good'
Price = 'Cheap'
}
Parallel {
Sequence {
# Add a custom entry to the hashtable
$WORKFLOW:Result += #{'Color' = 'Green'}
}
Sequence {
# Add a custom entry to the hashtable
$WORKFLOW:Result += #{'Fruit' = 'Kiwi'}
}
}
# Generate a PSCustomObject to work with later on
[PSCustomObject]$WORKFLOW:Result
}
}
Test-Fruit
You're supposed to define it as $WORKFLOW:var and repeat that use throughout the workflow to access the scope.
You could assign $Result to the output of the Parallel block and add the other properties afterwards :
$Result = Parallel {
Sequence {
# Add a custom entry to the hashtable
[Ordered]#{'Color' = 'Green'}
}
Sequence {
# Add a custom entry to the hashtable
[Ordered] #{'Fruit' = 'Kiwi'}
}
}
# Generate a PSCustomObject to work with later on
$Result += [Ordered]#{
Name = $I
Taste = 'Good'
Price = 'Cheap'
}
# Generate a PSCustomObject to work with later on
[PSCustomObject]$Result