Hash Table, Multiples values in Key, Foreach Loop Powershell - powershell

I have filled the keys with the necessary values,
Every key will have multiples values
$vms = #{}
$vms.template += $templateName
$vms.name += $vmName
$vms.EsxiHostName += $esxiHostName
$vms.datastore += $datastoreName
$vms.network += $networkName
$vms.FolderLocation += $folderName
$vms.vCPU += $vCPU
$vms.CoresPerCPU += $vmCores
$vms.Memory += $vmRam
$vms.IP += $vmIP
$vms.SubnetMask += $vmMask
$vms.gateway += $vmGateway
$vms.DNS1 += $vmDns1
$vms.DNS2 += $vmDns2
$vms.Description += $vmDescription
$vms.TrendMicroScanDay += $tmscanday
$vms.inventory_billing_owner += $inventoryBillingOwner
And now what I want to do is something like this because I want to use these variables in another commands.
foreach ($vm in $vms) {
#Assign Variables
$VCTemplate = $vm.template
$VMName = $vm.Name
$VMHost = $vm.EsxiHostName
$Datastore = $vm.datastore
$NetworkName = $vm.network
$FolderLocation = $vm.FolderLocation
$vCPU = $vm.vCPU
$CoresPerCPU = $vm.CoresPerCPU
$Memory = $vm.Memory
$VMIP = $vm.IP
$SubnetMask = $vm.SubnetMask
$GW = $vm.Gateway
$DNS1 = $vm.DNS1
$DNS2 = $vm.DNS2
$Description = $VM.Description
$TrendMicroScanDay = $VM.TrendMicroScanDay
$inventory_billing_owner = $VM.inventory_billing_owner
}
It seems foreach loop doesn't work this way and I try to find information about it but was not possible
Someone know how can I work with a Foreach Loop and a Hash Table with multiples values per key?
Thanks
EDIT:
Thanks Mclayton for answer, I tried your solutions
First I want to send you what is inside of $vms
PS C:\Users\me\Desktop> $vms
Name Value
---- ----- SubnetMask {255.255.255.0, 255.255.255.255} description {TEST, Test 2}
Memory {4, 8}
name {Name1, Test 2}
vCPU {4, 8}
ip {10.10.10.1, 20.20.20.1} datastore {vsanDatastore, vsanDatastore} dns2 {10.10.10.5, 20.20.20.5}
gateway {10.10.10.3, 20.20.20.3}
template {ESSQLTEMPLATE01, WIN 10 Template}
FolderLocation {Office Domain, SysAdmin LAB}
TrendMicroScanDay {Day5, Day5}
CoresPerCPU {4, 8}
dns1 {10.10.10.4, 20.20.20.4}
EsxiHostName {es1esxi01p, es1esxi02p}
network {servers, data2}
Then with the first option running this for test
for($i = 0; $i -lt $vms.template.Length; $i++ )
{
$VCTemplate = $vms.template[$i];
$VMName2 = $vms.Name[$i];
}
PS C:\Users\me\Desktop> $VCTemplate
WIN 10 Template
I'm getting the second value, maybe I didn’t understand what you were saying
And with the second option, I was thinking what to use in the foreach ($something in $something_else)
but I ran this:
$vm3 = #()
$vm3 += new-object PSCustomObject -Property ([ordered] #{
Template = $vms.template
Name = $vms.name
EsxiHostName = $vms.EsxiHostName
datastore = $vms.datastore
network = $vms.network
FolderLocation = $vms.FolderLocation
vCPU = $vms.vCPU
CoresPerCPU = $vms.CoresPerCPU
Memory = $vms.Memory
IP = $vms.IP
SubnetMask = $vms.SubnetMask
gateway = $vms.gateway
DNS1 = $vms.DNS1
DNS2 = $vms.DNS2
Description = $vms.Description
TrendMicroScanDay = $vms.TrendMicroScanDay
})
foreach ($vm in $vm3)
{
write-host 'This is '$vm.template
}
And this was the result
PS C:\Users\me\Desktop> foreach ($vm in $vm3)
{
write-host 'This is '$vm.template
}
This is ESSQLTEMPLATE01 WIN 10 Template

In your code, $vms is a single hashtable object, and if you foreach() over a single object the loop will only run once. The fact that all of $vms's properties (e.g. $vms.template) are arrays doesn't make any difference to this.
If you really need to use a single hastable with properties that are parallel arrays, what you'll need to do is something like:
for($i = 0; $i -lt $vms.template.Length; $i++ )
{
$VCTemplate = $vms.template[$i];
$VMName = $vms.Name[$i];
... etc ...
... now do stuff with the $i'th vm ...
write-host $vmName;
}
but a better alternative would be to create $vms as an array of objects with #() (note round brackets not squiggly ones) - e.g.
$vms = #()
foreach( $something in $something_else )
{
$vms += new-object PSCustomObject -Property ([ordered] #{
Template = $something.template
Name = $something.name
... etc ...
})
}
and then you can iterate over $vms:
foreach ($vm in $vms)
{
write-host $vm.Name
}

Related

Powershell Wrong output

guys i need help here, i want to return the $Location002 and $Location003 content look what is in output
$Location = "westus2"
$Location002 = "westeurope"
$Location003 = "eastasia"
[int]$VMCount = Read-Host "How many VMs?"
1..$VMCount | ForEach-Object {
$i = $_
# define name for VM, will be used for other resources
if ($i -eq 1) {
$locname = "$Location"
Write-Output $locname
}
else {
$locname = $("Location00" + "$i")
Write-Output $locname
}
}
output :
PS C:\Users\Marouane\Desktop\testpowershell> c:\Users\Marouane\Desktop\testpowershell\test.ps1
How many VMs?: 3
westus2
Location002
Location003
PS C:\Users\Marouane\Desktop\testpowershell>
i need to output westeurope and eastasia
Using a separate variable for each value in a group or list of things is a bit of an anti-pattern, you'll want to put them all in an array together instead:
# Define array of possible locations
# `$Locations[0]` will resolve to `westus2`
# `$Locations[1]` will resolve to `westeurope`, etc.
$Locations = #(
"westus2"
"westeurope"
"eastasia"
)
[int]$VMCount = Read-Host "How many VMs?"
1..$VMCount | ForEach-Object {
# Define the VM name
$name = "VirtualMachine$_"
# Pick next location from the $Locations array
# the % ensures we "wrap around" when we reach the end
$location = $Locations[($_ - 1) % $Locations.Length]
# Output a new object with Name + Chosen Location
[pscustomobject]#{
VMName = $name
Location = $location
}
}
Output for 3 VMs:
How many VMs?: 3
VMName Location
------ --------
VirtualMachine1 westus2
VirtualMachine2 westeurope
VirtualMachine3 eastasia
This is not how I would do it, but the more immediate problem is you're assigning a concatenated string to $location and writing to the output stream. I think what you want to do is reference the value of the earlier variable.
There are some clever syntaxes for that. I struggle to remember them. However below would be a start.
$Location = "westus2"
$Location002 = "westeurope"
$Location003 = "eastasia"
[int]$VMCount = Read-Host "How many VMs?"
1..$VMCount | ForEach-Object {
$i = $_
# define name for VM, will be used for other resources
if ($i -eq 1) {
$name = "$VMName"
$locname = "$Location"
Write-Output $locname
}
else {
$name = "$VMName" + "00" + "$i"
$locname = (Get-Variable ("Location00" + "$i")).Value
Write-Output $locname
}
}
Update With Alternative:
I'm still not sure what the goal is, but based on the original sample it would seem there's a 1 to 1 relationship between the location# and the VM number. That said if you go past the number of VMs you would have to adjust this to pick according to the intended pattern...
$Locations = 'westus2', 'westeurope', 'eastasia'
[int]$VMCount = Read-Host 'How many VMs?'
For( $i = 0; $i -lt $VMCount ; ++$i )
{
$Locations[$i]
}
Further Update:
Respective to Mathias's good answer :
$Locations = 'westus2', 'westeurope', 'eastasia'
[int]$VMCount = Read-Host 'How many VMs?'
For( $i = 0; $i -lt $VMCount ; ++$i )
{
$Locations[ $i % $Locations.Count ]
}
Using the modulus operator in this pattern is very efficient for distributing one list over another. I wrote a small post about My Modulus Obsession with this and some other uses.
You'll need to retrieve the variable's content with Get-Variable. You can also avoid the extra step of making an $i variable and instead use the automatic variable $_
$Location = "westus2"
$Location002 = "westeurope"
$Location003 = "eastasia"
[int]$VMCount = Read-Host "How many VMs?"
1..$VMCount | ForEach-Object {
# define name for VM, will be used for other resources
if ($_ -eq 1) {
$name = "$VMName"
$locname = "$Location"
Write-Output $locname
}
else {
$name = "$VMName" + "00" + "$_"
$locname = "Location00" + "$_"
Write-Output (get-variable $locname).value
}
}

How can i split up the results of this hashtable search?

I'm trying to use this to compare my AD NT hashdump with https://haveibeenpwned.com/Passwords hashes.
I'm having trouble with the results grouping multiple usernames with the same password together.
the code:
param(
[Parameter(Mandatory = $true)]
[System.IO.FileInfo] $ADNTHashes,
[Parameter(Mandatory = $true)]
[System.IO.FileInfo] $HashDictionary
)
#>
process {
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
#Declare and fill new hashtable with ADNThashes. Converts to upper case to
$htADNTHashes = #{}
Import-Csv -Delimiter ":" -Path $ADNTHashes -Header "User","Hash" | % {$htADNTHashes[$_.Hash.toUpper()] += #($_.User)}
#Create empty output object
$mrMatchedResults = #()
#Create Filestream reader
$fsHashDictionary = New-Object IO.Filestream $HashDictionary,'Open','Read','Read'
$frHashDictionary = New-Object System.IO.StreamReader($fsHashDictionary)
#Iterate through HashDictionary checking each hash against ADNTHashes
while (($lineHashDictionary = $frHashDictionary.ReadLine()) -ne $null) {
if($htADNTHashes.ContainsKey($lineHashDictionary.Split(":")[0].ToUpper())) {
$foFoundObject = [PSCustomObject]#{
User = $htADNTHashes[$lineHashDictionary.Split(":")[0].ToUpper()]
Frequency = $lineHashDictionary.Split(":")[1]
Hash = $linehashDictionary.Split(":")[0].ToUpper()
}
$mrMatchedResults += $foFoundObject
}
}
$stopwatch.Stop()
Write-Verbose "Function Match-ADHashes completed in $($stopwatch.Elapsed.TotalSeconds) Seconds"
}
end {
$mrMatchedResults
}
}
I tried commenting out | % {$htADNTHashes[$_.Hash.toUpper()] += #($_.User)} which seems to be close, but that somehow removed the Frequency column.
The results look like this:
User Frequency Hash
---- --------- ----
{TestUser2, TestUser3} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser1} 1 H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2
I would like them separated:
User Frequency Hash
---- --------- ----
{TestUser2} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser3} 20129 H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1H1
{TestUser1} 1 H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2H2
i'm sure this is a simple change, but i have very little powershell experience.
The suggestion to change $FormatEnumerationLimit to -1 is not what i want either, that just fixes the list truncating.
{user1, user2, user3...}
while (($lineHashDictionary = $frHashDictionary.ReadLine()) -ne $null) {
if($htADNTHashes.ContainsKey($lineHashDictionary.Split(":")[0].ToUpper())) {
$Users = $htADNTHashes[$lineHashDictionary.Split(":")[0].ToUpper()]
foreach($User in $Users){
$foFoundObject = [PSCustomObject]#{
User = $User
Frequency = $lineHashDictionary.Split(":")[1]
Hash = $linehashDictionary.Split(":")[0].ToUpper()
}
$mrMatchedResults += $foFoundObject
}
}
}

Generate 2 different list in one foreach loop with powershell

I stucked in foreach part.I couldn't find any solution for generating 2 different lists in one foreach loop.I used 2 foreach but it didn't help.Below side I shared my desire output.
My code:
$InStuff = #'
a
b
c
'#.Split("`n").Trim()
$InStuff2 = #'
1
2
3
'#.Split("`n").Trim()
$SPart_1 = 'application="'
$SPart_2 = ' path='
$SPart_3 = ' name='
$SPart_4 = ' application'
foreach ($IS_Item in $InStuff) {
foreach ($IS2_Item in $InStuff2) {
$UName = $IS_Item
$UName2 = $IS2_Item
$Sentence = -join (
$SPart_1, $UName,
$SPart_2, $UName2,
$SPart_3, $UName2,
$SPart_4
)
''
$Sentence
}
}
Fail output :
application="a path=1 name=1 application
application="a path=2 name=2 application
application="a path=3 name=3 application
application="b path=1 name=1 application
application="b path=2 name=2 application
application="b path=3 name=3 application
application="c path=1 name=1 application
application="c path=2 name=2 application
application="c path=3 name=3 application
My desire output :
application="a path=1 name=1 application
application="b path=2 name=2 application
application="c path=3 name=3 application
Thank you
use a for loop:
$InStuff = #'
a
b
c
'#.Split("`n").Trim()
$InStuff2 = #'
1
2
3
'#.Split("`n").Trim()
$SPart_1 = 'application="'
$SPart_2 = ' path='
$SPart_3 = ' name='
$SPart_4 = ' application'
for ($i = 0; $i -lt $InStuff.count; $i++) {
$Sentence = -join (
$SPart_1, $InStuff[$i],
$SPart_2, $InStuff2[$i],
$SPart_3, $InStuff2[$i],
$SPart_4
), ''
$Sentence
}
This will likely go wrong if your input arrays are not the same length, so it is not that safe. Perhaps using a hash or custom object would be a better idea:
$arr = #()
$arr += new-object PSCustomObject -property #{application='a';path=1;name=1}
$arr += new-object PSCustomObject -property #{application='b';path=2;name=2}
$arr += new-object PSCustomObject -property #{application='c';path=3;name=3}
$arr | % { 'application="{0} path={1} name={2}' -f $_.application, $_.path, $_.name }
#arco444 is right, no matter what you will have problems if your lists are different lengths. You should reconsider how you are collecting and formatting the data. Here is an alternative method:
$InStuff = "a","b","c"
$InStuff2 = 1,2,3
$listCount = $InStuff.Count
$x = 0
do {
$strOut = "application= `"path = {0} name = {1} application`"" -f $InStuff[$x], $InStuff2[$x]
$strOut
$x++
}
while ( $x -lt $listCount )
Not sure what you want with a stray " in there, I've added one to enclose the output:
application= "path = a name = 1 application"
application= "path = b name = 2 application"
application= "path = c name = 3 application"
If you plan to use this output for further processing by PowerShell, like putting it in a csv with Export-Csv then you should forgo the application text and create an object instead:
$InStuff = "a","b","c"
$InStuff2 = 1,2,3
$listCount = $InStuff.Count
$x = 0
do {
[pscustomobject]#{
path = $InStuff[$x]
name = $InStuff2[$x]
}
$x++
}
while ( $x -lt $listCount )
While that's not exactly what you are asking for, it's been my experience that data in this format is far more useful:
path name
---- ----
a 1
b 2
c 3
you can add lines to
[pscustomobject]#{
path = $InStuff[$x]
name = $InStuff2[$x]
}
for the additional text (if it's a must) and do something like this:
[pscustomobject]#{
type = "application"
path = $InStuff[$x]
name = $InStuff2[$x]
}
and that will add a column for the word application

how to use a FOR loop variable to help define another variable

I am new to powershell scripts and not sure how to achieve the below:
$finalArray = #()
$tempArray0 = 'A'
$tempArray1 = 'B'
$tempArray2 = 'C'
FOR (i=0; i -eq 5; i++) {$finalArray += $tempArray[i]}
$finalArray
Output Should be:
A
B
C
If the variable name is itself variable, you'll have to use the Get-Variable cmdlet to retrieve its value:
$finalArray = #()
$tempArray0 = 'A'
$tempArray1 = 'B'
$tempArray2 = 'C'
for($i=0; $i -le 2; $i++) {
$finalArray += (Get-Variable "temparray$i" -ValueOnly)
}
$finalArray
If you want to create variables with variable names, use the New-Variable cmdlet:
$Values = 'A','B','C'
for($i = 0; $i -lt $Values.Count; $i++){
New-Variable -Name "tempvalue$i" -Value $Values[$i]
}
which would result in:
PS C:\> $tempvalue1
B
Although the above will solve the example you've presented, I can think of very few cases where you wouldn't be better of using a [hashtable] instead of variable variable names - they're usually an over-complication, and you'll end up with unnecessary code anyways because you need to calculate the variable names at least twice (during creation and again when reading the value).
From the comments, it sounds like you're trying to generate input for a password generator. This can be simplified grossly, without resorting to variable variable names:
# Create a hashtable and generate the characters
$CharArrays = #{
Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()
Numbers = 0..9
}
# Generate some letters for the password
$PasswordChars = $CharArrays['Letters'] |Get-Random -Count 10
# Generate a few digits
$PasswordChars += $CharArrays['Numbers'] |Get-Random -Count 4
# Shuffle them around a bit
$PasswordChars = $PasswordChars |Sort-Object {Get-Random}
# Create your password
$Password = $PasswordChars -join ''

Creating dynamic variable array names and then adding object to them

What I'm trying to do is create array variable names dynamically, and then with a loop, add the object to its relevant array based on the hash table value being equal to the counter variable.
$hshSite = #{} # Values like this CO,1 NE,2 IA,3
$counter = $hshSite.count
For($i = $counter; $i -gt 0; $i--) {
New-Variable -Name "arr$i" -Value #()
}
If $counter = 3, I would create arrays $arr1, $arr2, $arr3
$csv = Import-CSV....
ForEach ($x in $csv) {
#if $hshSite.Name = $x.location (ie CO), look up hash value (1),
and add the object to $arr1. If $hshSite.Name = NE, add to $arr2
I tried creating the dynamic arrays with New-Variable, but having issues trying to add to those arrays. Is it possible to concatenate 2 variables names into a single variable name? So taking $arr + $i to form $arr1 and $arr2 and $arr3, and then I can essentially just do $arr0 += $_
The end goal is to group things based on CO, NE, IA for further sorting/grouping/processing. And I'm open to other ideas of getting this accomplished. Thanks for your help!
Just make your hash table values the arrays, and accumulate the values to them directly:
$Sites = 'CO','NE','IA'
$hshSite = #{}
Foreach ($Site in $Sites){$hshSite[$Site] = #()}
ForEach ($x in $csv)
{
$hshSite[$x.location] += <whatever it is your adding>
}
If there's a lot of entries in the csv, you might consider creating those values as arraylists instead of arrays.
$Sites = 'CO','NE','IA'
$hshSite = #{}
Foreach ($Site in $Sites){ $hshSite[$Site] = New-Object Collections.Arraylist }
ForEach ($x in $csv)
{
$hshSite[$x.location].add('<whatever it is your adding>') > $nul
}
You could quite easily do add items to a dynamically named array variable using the Get-Variable cmdlet. Similar to the following:
$MyArrayVariable123 = #()
$VariableNamePrefix = "MyArrayVariable"
$VariableNameNumber = "123"
$DynamicallyRetrievedVariable = Get-Variable -Name ($VariableNamePrefix + $VariableNameNumber)
$DynamicallyRetrievedVariable.Value += "added item"
After running the above code the $MyArrayVariable123 variable would be an array holding the single string added item.