Check for empty value in Array - arrays

How do I check for empty values in an hashtable, and list the item name as well ?
I could do if ($Vars.ContainsValue($null)) but this does not get me what item that has a $null value
$Vars = #{
1 = "CustomerID";
2 = "DepartmentID";
3 = "Environment";
4 = "JoinDomain";
5 = ""
}
if I do a foreach ($var in $vars) I get the whole hashtable?

First of all this is not an array, because those are written as #('element1', 'element2'). This concerns a HashTable which is indicated as #{} and is enumerated by the GetEnumerator() method.
After that method it's simply a matter of filtering out what you need with the key and/or the value property.
$Vars = #{
1 = "CustomerID";
2 = "DepartmentID";
3 = "Environment";
4 = "JoinDomain";
5 = ""
}
$VerbosePreference = 'Continue'
$Vars.GetEnumerator() | Where-Object {
-not $_.Value
} | ForEach-Object {
Write-Verbose "The key '$($_.Key)' has no value"
# Other code for handling the key with no value
}

Related

How to insure json stays array

I want to remove an array element from json array (PSObject) if value matches as follows:
$code = 12345
$myObject = #{ ArrayPair= #(#{ code = 12345; perm = "RW" }, #{ code = 23456; perm = "RW" })}
if ($true) { # $revoke
$myObject.ArrayPair = $myObject.ArrayPair | Where-Object -FilterScript {$_.code -ne $code}
}
At the start ArrayPair has 2 array elements, after executing the filter, ArrayPair is no longer an array but rather an object with two elements. How can I keep it as an array so I can continue to add new pairs to the array?
json Values before and After removal:
Before value:
{"ArrayPair": [{"perm": "RW","code": 12345},{"perm": "RW","code": 23456}]}
After Value removal
{"ArrayPair": { "perm": "RW", "code": 23456 }}
You can force the object to stay an array like this:
$code = 12345
$myObject = #{ ArrayPair= #(#{ code = 12345; perm = "RW" }, #{ code = 23456; perm = "RW" })}
[array]$myObject.ArrayPair = $myObject.ArrayPair | Where-Object -FilterScript {$_.code -ne $code}
$myObject.ArrayPair.GetType()
#Returns
#IsPublic IsSerial Name BaseType
#-------- -------- ---- --------
#True True Object[] System.Array
To add additional entries to your array you have to try it like this:
$myObject.ArrayPair += #{code = 2134; perm= "RR"}
This way you can add entries to the array and the result looks like this:
PS C:\> $myObject.ArrayPair
Name Value
---- -----
code 23456
perm RW
code 2134
perm RR
Please be aware that += doens't really add objects to the array, but instead recreates the array with new values.
If you try to add the objects via $myObject.ArrayPair.Add(#{code = 2134; perm= "RR"}) you get an error.
Please take a look at this answer for further explanations:
PowerShell Array.Add vs +=
I while deleting elements if there was just one left, I found that I had to double force the object to make sure that it remained an array type:
[array]$temp = $result.data.app.roles.admin | Where-Object -FilterScript {$_.club -ne $ClubNo}
$result.data.app.roles.admin = [array]($temp)

PowerShell array getting new entries

In this snippet I have a function (FDiskScan) that gets a computer name as an input and should return an array of objects.
function FDiskScan ([String] $name)
{
$outarray = [System.Collections.ArrayList]#()
$diskscan = Get-WmiObject Win32_logicaldisk -ComputerName $name
foreach ($diskobj in $diskscan)
{
if($diskobj.VolumeName -ne $null )
{
$max = $diskobj.Size/1024/1024/1024
$free = $diskobj.FreeSpace/1024/1024/1024
$full = $max - $free
$obj = #{
'ID' = $diskobj.deviceid
'Name' = $diskobj.VolumeName
'TotalSpace' = $max
'FreeSpace' = $free
'OccupiedSpace' = $full }
$TMP = New-Object psobject -Property $obj
$outarray.Add($TMP)
}
}
return $outarray
}
$pc = "INSERT PC NAME HERE"
$diskdata = [System.Collections.ArrayList]#()
$diskdata = FDiskScan($pc)
foreach ($disk in $diskdata)
{
Write-Host "Disco: " $disk.ID
Write-Host "Espaço Total: " ([math]::Round($disk.TotalSpace, 2)) "GB"
Write-Host "Espaço Ocupado: " ([math]::Round($disk.OccupiedSpace, 2)) "GB"
Write-Host "Espaço Livre" ([math]::Round($disk.FreeSpace, 2)) "GB" "`n"
}
Within the function with debugging and going into the variables I can see that everything is alright, and when the array gets out of the function and into the script scope it adds 2 more entries.
While in debug mode it tells me that $outarry within FDiskScan has the two disks that I have on my system organised as they should be.
However on:
$diskdata = FDiskScan($pc)
It says that it has an entry of value 0 on index 0 and of value 1 on index 1, then the disks follow suit, first disk C: in index 3 and disk D in index 4.
The expected behaviour was for index 0 and 1 having disks C and D respectively not a phantom 0 and 1 entries.
When adding an object to an array list in PowerShell (i.e. $outarray.Add($TMP)) the index, the object was added at, gets returned. As you don't assign the return value to a variable the function returns a System.Array containing the indexes and the entries of the array list returned by return $outarray. That's the reason why your functions return value contains 4 elements. Furthermore your functions return value in this case is not of type System.Collections.ArrayList but of type System.Array.
To avoid that behaviour do the following.
$null = $outarray.Add($TMP);
You are seeing 0, 1 because of this line - $outarray.Add($TMP). Change it to $outarray.Add($TMP) | Out-Null. I think PowerShell is printing the index when adding to the array.

Working two Array PowerShell

I have two arrays: array1 [POP1, POP2, POP3 .... POP30] and array2 [61,61,62 ... 61]. I need to create a new object with value 62 and its POP.
In this example:
POP3 62.
I am simplifying the explanation because I've already been able to get the value from the database.
Can someone help me?
Code:
$target = #( )
$ini = 0 | foreach {
$apiurl = "http://xxxxxxxxx:8080/fxxxxp/events_xxxx.xml"
[xml]$ini = (New-Object System.Net.WebClient).downloadstring($apiurl)
$target = $ini.events.event.name
$nodename = $target
$target = $ini.events.event.statuscode
$statuscode = $target
}
$column1 = #($nodename)
$column2 = #($statuscode)
$i = 0
($column1,$column2)[0] | foreach {
New-Object PSObject -Property #{
POP = $Column1[$i]
Status = $column2[$i++]
} | ft -AutoSize
I really couldn't figure out what you were trying to do, but you definitely over complicated it. Here is what I thought of your code:
# Here you have an empty array
$target = #( )
# Here you set call a Foreach, but you don't even need it
$ini = 0 | foreach {
$apiurl = "http://xxxxxxxxx:8080/fxxxxp/events_xxxx.xml"
[xml]$ini = (new-object System.Net.WebClient).downloadstring($apiurl)
# You duplicated variables here. Just set $nodename = $ini.events.event.name
$target = $ini.events.event.name
$nodename = $target
# You duplicate variables here. Just set $statuscode = $ini.events.event.name
$target = $ini.events.event.statuscode
$statuscode = $target
}
# You should already have arrays, so now you're making making more arrays duplicating variables again
$column1 = #($nodename)
$column2 = #($statuscode)
# counter, but you won't need it
$i = 0
# So here, youre making a new array again, but this contains two nested arrays. I don't get it.
($column1,$column2)[0] | foreach {
New-Object PSObject -Property #{
POP = $Column1[$i]
Status = $column2[$i++]
} | ft -AutoSize
} # You were missing a closing bracket for your foreach loop
Here is a solution that should probable work for you:
# Download the file
$apiurl = "http://xxxxxxxxx:8080/fxxxxp/events_xxxx.xml"
[xml]$ini = (New-Object System.Net.WebClient).DownloadString($apiurl)
# Set arrays
$nodename = $ini.events.event.name
$statuscode = $ini.events.event.statuscode
# Create $TableValues by looping through one array
$TableValues = foreach ( $node in $nodename )
{
[pscustomobject] #{
# The current node
POP = $node
# use the array method IndexOf
# This should return the position of the current node
# Then use that index to get the matching value of $statuscode
Status = $statuscode[$nodename.IndexOf($node)]
}
}
# Add a custom value
$TableValues += [pscustomobject] #{
POP = 'POP100'
Status = 100
}
$TableValues | Format-Table -AutoSize
Assuming that your intent is to create an array of custom objects constructed from the pairs of corresponding elements of 2 arrays of the same size:
A concise pipeline-based solution (PSv3+; a for / foreach solution would be faster):
$arr1 = 'one', 'two', 'three'
$arr2 = 1, 2, 3
0..$($arr1.Count-1) | % { [pscustomobject] #{ POP = $arr1[$_]; Status = $arr2[$_] } }
This yields:
POP Status
--- ------
one 1
two 2
three 3

How to search a collection with an array in Powershell

I have an array of MailItems from Outlook. I want to search each mail item and return the Subject and a Category based on a list of search terms contained in an array - in the example called $searchArray.
For example:
$mailbox = "my.mailbox#example.com"
$outlook = New-Object -com Outlook.Application
$ns = $outlook.GetNamespace("MAPI")
$inbox = $ns.Folders.Item($mailbox).Folders.Item("Inbox")
$searchItems = $inbox.Folders.Item("MySubFolder").Folders.Item("MyNestedSubFolder").Items
$searchArray = (
"Category1", ("searchTerm1","searchTerm2","searchTerm3"),
"Category2", ("searchTerm4","searchTerm5"),
"Category3", ("searchTerm6")
)
foreach ($msg in $searchItems) {
$msg | select Subject, # <Category where email address contains one of the search terms>
}
I want to return the Subject, and then a column called Category which will look at the $msg.SenderEmailAddress and if any of the searchTerms in the $searchArray is contained within the address, return the category that had that search term in it.
For example if one of the SenderEmailAddress values was "searchTerm2#somewhere.com" then return Category1 as the Category.
I would flip that array on its head and create a hashtable from it. Then use the first matching search term as a lookup key for the category:
$searchArray = (
"Category1", ("searchTerm1","searchTerm2","searchTerm3"),
"Category2", ("searchTerm4","searchTerm5"),
"Category3", ("searchTerm6")
)
# Create hashtable
$searchTable = #{}
# Populate hash table with values from array
for($i=0;$i-lt$searchArray.Count;$i+=2){
foreach($term in $searchArray[$i+1])
{
$searchTable[$term] = $searchArray[$i]
}
}
# Select Category based on first matching search term
$msg |Select-Object Subject,#{Name="Category";Expression={
$sender = $_.SenderEmailAddress
$searchTable[$($searchTable.Keys |Where-Object{$sender -like "*$_*"} |Select -First 1)]
}
}
Still need to use a calculated expression just as Mathias did (It's really the simple way). However I wanted to show an approach where you had a custom object array for the $searchArray. If you were to tailor it from scratch it would look like this. I also converted the terms into regex pattern matches since you say they are unique. Only caveat there is you need to be sure that there are no regex meta-characters in your search terms.
$searchArray = (
[pscustomobject]#{
Category = "1"
Pattern = "searchTerm1|searchTerm2|searchTerm3"
},
[pscustomobject]#{
Category = "2"
Pattern = "searchTerm4|searchTerm5"
},
[pscustomobject]#{
Category = "3"
Pattern = "searchTerm6"}
)
foreach ($msg in $searchItems) {
$msg | select Subject, #{
Name="Category";
Expression={$searchArray | Where-Object{$msg.SenderEmailAddress -match $_.pattern } | Select-Object -ExpandProperty Category}
}
}
Solution is dependant on PowerShell 3.0 from the type accelerator [pscustomobject]. Could easily bring it back to 2.0 if need be.
To showcase similar structure using 2.0 and automatic conversion of you array to one that works with my code.
$newSearchArray = for($categoryIndex=0;$categoryIndex-lt$searchArray.Count;$categoryIndex+=2){
New-Object -TypeName pscustomobject -Property #{
Category = $searchArray[$categoryIndex]
Pattern = ($searchArray[$categoryIndex+1] | ForEach-Object{[regex]::Escape($_)}) -join "|"
}
}
Now the search terms are automatically escaped and joined into a search pattern.
Using a switch:
$mailbox = "my.mailbox#example.com"
$outlook = New-Object -com Outlook.Application
$ns = $outlook.GetNamespace("MAPI")
$inbox = $ns.Folders.Item($mailbox).Folders.Item("Inbox")
$searchItems = $inbox.Folders.Item("MySubFolder").Folders.Item("MyNestedSubFolder").Items
foreach ($msg in $searchItems) {
$object = $msg | select Subject,Category # <Category where email address contains one of the search terms>
Switch -Regex ($msg.SenderEmailAddress)
{
'searchTerm1|searchTerm2|searchTerm3' { $object.Catetory = 'Category1' ; break }
'searchTerm4|searchTerm5' { $object.Catetory = 'Category2' ; break }
'searchTerm6' { $object.Catetory = 'Category3' ; break }
}
$object
}

How to create and populate an array in Powershell based on a dynamic variable?

I've been struggling with this for a couple of days, and I'm not sure how to conquer it. I need to do the following:
Import a csv of users with the following values:
ID, Name, Region
Create an array based on the Region values that I can then use to populate with ID's and Names with that region, ie.
Array_SEA
AA_SCOM, Adam Andrews, SEA
Array_OAK
BB_SCOM, Bob Barker, OAK
Here's the code I've got right now:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#()
foreach ($vitem in $list2)
{
$arraylist += New-Object PsObject -Property #{'Array' = "Array_" + $vitem.bu}
}
foreach ($varray in $arraylist)
{
$arr = new-variable -Name $varray
$arr.value += $varray.array
$arr
}
This produces the following error for records with a duplicate regions:
New-Variable: A variable with name '#{Array=Array_SCA}' already exists.
I'm also getting the following when it tries to add values:
Property 'value' cannot be found on this object; make sure it exists and is settable.
I get that I'm not actually creating arrays in the second section, but I'm not sure how to pass the output of the variable to an array name without turning the variable declaration into the array name, if that makes sense.
I've tried the following with hash tables, and it gets closer:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist =#{}
foreach ($vitem in $list2){$arraylist[$vitem.bu] = #()}
foreach ($record in $list2)
{
$arraylist[$vitem.bu] += ($record.SCOMID,$record.Name,$record.BU)
Write-host "Array: "
$arraylist[$vitem.bu]
write-host ""
}
The output on this shows no errors, but it just keeps showing the added fields for all of the records for each iteration of the list, so I don't think that it's actually assigning each unique BU to the array name.
I like the hashtable-approach, but I would finetune it a little. Try:
$list2 = ipcsv .\TSE_Contact_List.csv | sort-object BU
$arraylist = #{}
foreach ($vitem in $list2){
if($arraylist.ContainsKey($vitem.BU)) {
#Array exists, add item
$arraylist[($vitem.BU)] += $vitem
} else {
#Array not found, creating it
$arraylist[($vitem.BU)] = #($vitem)
}
}
#TEST: List arrays and number of entries
$arraylist.GetEnumerator() | % {
"Array '$($_.Key)' has $($_.Value.Count) items"
}
You could also use Group-Object like:
$list2 = ipcsv .\TSE_Contact_List.csv | Group-Object BU
#TEST: List groups(regions) and number of entries
$list2 | % {
"Region '$($_.Name)' has $(#($_.Group).Count) items"
}

Resources