I have a custom PS Object that is something like the below:
ID Folder
MyServer01 \\Server\Share\Share\MyServer01
MyServer02 \\Server\Share\Share\MyServer02
Naturally the object itself is rather large, with over 1000 entries. I need to be able to select a specific row of the object based on querying the ID.
I thought something like this would work but I'm not having much luck:
$obj | Select-Object | Where-Object ($_.ID -eq "MyServer01")
I need it to return the entire row, so the above (assuming it worked) would return:
MyServer01 \\Server\Share\Share\MyServer01
EDIT:
foreach ($mf in $Folders.Tables[0]) {
$Info = New-Object System.Object
$Info | Add-Member -Type NoteProperty -Name ID -Value $mf.ID
$Info | Add-Member -Type NoteProperty -Name Folder -Value $mf.Folder
$obj += $Info
}
Use a hashtable for storing your objects:
$obj = #{}
foreach ($mf in $Folders.Tables[0]) {
$Info = New-Object -Type System.Object
$Info | Add-Member -Type NoteProperty -Name ID -Value $mf.ID
$Info | Add-Member -Type NoteProperty -Name Folder -Value $mf.Folder
$obj[$mf.ID] = $Info
}
Don't append to an array in a loop, as that tends to perform poorly.
If your code doesn't depend on the objects being created explicitly as System.Object I'd also recommend to create them as custom objects:
$obj = #{}
foreach ($mf in $Folders.Tables[0]) {
$Info = New-Object -Type PSCustomObject -Property #{
'ID' = $mf.ID
'Folder' = $mf.Folder
}
$obj[$mf.ID] = $Info
}
Related
I have written a little script which should be export the output as CSV.
Here is my script:
$Jobs = Get-VBRJob
foreach ($Job in $Jobs) {
$JobName = $Job.Name
$Objects = $Job.GetObjectsInJob()
$RestorePoints = Get-VBRRestorePoint -Backup $JobName
$Day = $Job.ScheduleOptions.OptionsDaily.DaysSrv
$RP = $RestorePoints.Count
$VM = $Objects.Name
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name "JobName" -Value $JobName
$obj | Add-Member -MemberType NoteProperty -Name "Objects" -Value $Objects
$obj | Add-Member -MemberType NoteProperty -Name "RestorePoints" -Value $RestorePoints
$obj | Add-Member -MemberType NoteProperty -Name "Day" -Value $Day
$obj | Add-Member -MemberType NoteProperty -Name "VM" -Value $VM
}
$obj | Export-Csv $path -NoType
I read something about doing it via New-Object PSObject, so I tried this, but the CSV has only one line and returns the types of the attributes not the value. Only "JobName" and "VM" is working fine.
Can anyone help me to get the value of "Objects", "RestorePoints" and "Day" into CSV?
$obj contains only the object you just created, so after the loop completes, the variable holds the last object created in the loop, which is then exported to the CSV. A better approach would be outputting the created objects in the loop and collecting the loop output in a variable. I'd also recommend avoiding Add-Member unless you need to add members to an object that had been created elsewhere.
$obj = foreach ($Job in $Jobs) {
...
New-Object -Type PSObject -Property #{
'JobName' = $JobName
'Objects' = $Objects
'RestorePoints' = $RestorePoints
'Day' = $Day
'VM' = $VM
}
}
Also, if the values you're assigning to the new object's properties are objects themselves, PowerShell will export the string representation of those objects to the CSV, which usually is the full name of the object's class. If you want particular values in the output you probably need to expand those further (e.g. 'VM' = $VM.Name). What exactly you need to do there depends on the actual object, though, so I can't help much there without knowing more about the structure of the objects.
Testing that individual registry values exist and checking them for the proper data is not overly-complicated in Powershell using Get-Item and Get-ItemProperty. What I would like to be able to do is check a large number of registry values for existence as well as data.
For example, given the following registry entries:
HKLM:\SOFTWARE\Breakfast\1001 = 3 [DWORD]
HKLM:\SOFTWARE\Breakfast\1003 = 3 [DWORD]
HKLM:\SOFTWARE\Breakfast\1004 = 3 [DWORD]
HKLM:\SOFTWARE\Breakfast\1005 = 1 [DWORD]
A big, ugly script that performs the test on each value and data individually isn't complicated, but I'd love to see if it is possible to throw a friendly name, registry path/value, and the desired data into an array so that we could have a function that would perform our tests.
The array could look something like this:
$registry_list = #()
$registry_list.gettype()
$registry_list += ,#('Poptarts','HKLM:\SOFTWARE\Breakfast\','1001','3')
$registry_list += ,#('Toast','HKLM:\SOFTWARE\Breakfast\','1002','3')
$registry_list += ,#('Muffins','HKLM:\SOFTWARE\Breakfast\','1003','3')
$registry_list += ,#('Bagels','HKLM:\SOFTWARE\Breakfast\','1004','3')
$registry_list += ,#('Biscuits','HKLM:\SOFTWARE\Breakfast\','1005','3')
Since I'm new to arrays, I have no idea how to feed these into a function that can output something showing only the errors
Toast
Value Missing (HKLM:\SOFTWARE\Breakfast\1002)
Biscuits
Value Set Incorrectly (HKLM:\SOFTWARE\Breakfast\1005) Desired: 3. Actual: 1
If anyone can weigh in to help figure out how a function or similar can iterate through each of the registry values it would be appreciated. The examples here are short, but I really want to be able to run hundreds of registry values through this test.
I've never been a huge fan of multidimensional arrays in PowerShell. They end up feeling very flaky or unstable. Arrays in PowerShell also suck because when you use the += operator, the system has to build a new array with the new element and then throw the old array away. It's computationally expensive.
For this case, I would create an ArrayList, and add the arrays to that. I would also probably use a HashTable for each item so I could use a name instead of an index number to refer to the items:
$registry_list = New-Object System.Collections.ArrayList;
# Use the Add() function to add records. The [void] type is here because the function
# normally returns the number of records in the ArrayList, and we don't want that to go to output.
[void]$registry_list.Add(#{Value='Poptarts';Path='HKLM:\SOFTWARE\Breakfast';Key='1001';Data='3'});
[void]$registry_list.Add(#{Value='Toast';Path='HKLM:\SOFTWARE\Breakfast';Key='1002';Data='3'});
$registry_list | ForEach-Object {
$RegistryPath = Join-Path -Path $_.Path -ChildPath $_.Key;
if (Test-Path -Path $RegistryPath) {
Write-Host "Path '$RegistryPath' exists."
$RegistryData = (Get-ItemProperty -Path $RegistryPath).($_.Value)
if ($RegistryData -eq $_.Data) {
Write-Host "Check OK. Value $($_.Value) data is set to '$RegistryData'. Desired data is '$($_.Data)'."
}
else {
Write-Host "Check Failed. Value $($_.Value) data is set to '$RegistryData'. Desired data is '$($_.Data)'."
}
}
else {
Write-Host "Path '$RegistryPath' does not exist."
}
}
Note that I have not rigorously tested this code. Notably, I'm a bit skeptical about how correct if ($RegistryData -eq $_.Data) is for all cases.
I would store the data in a CSV file:
friendlyName,key,value,data
Poptarts,HKLM:\SOFTWARE\Breakfast\,1001,3
Toast,HKLM:\SOFTWARE\Breakfast\,1002,3
Muffins,HKLM:\SOFTWARE\Breakfast\,1003,3
Bagels,HKLM:\SOFTWARE\Breakfast\,1004,3
Biscuits,HKLM:\SOFTWARE\Breakfast\,1005,3
Then loop over each row in the file
foreach($row in Import-Csv .\breakfast.csv)
{
# retrieve key
$key = Get-Item $row.key
# retrieve value
$value = $key |Get-ItemProperty -Name $row.value
# compare data
$valid = $value."$($row.value)" -eq $row.data
# output result
$outParams = #{
Object = if($valid){"$($row.friendlyName) is correct"} else {"$($row.friendlyName) is incorrect"}
ForegroundColor = #('Red','Green')[+$valid]
}
Write-Host #outParams
}
I'll leave implementation of error handling and nicer output an excercise for OP :-)
I'd highly suggest when using large arrays to use an array of objects. Creating objects makes referencing the different parts of your array very easy using properties.
You can also then use a template server that has the proper values already in place to then build the object/array of objects to then use to validate other systems.
Here is a basic example of building an object. It is more efficient to create a simple function that builds these objects for you so that you don't have so much code repetition, but this is basic way to go about it. If you want a more advanced method of creating objects, let me know and I'll post an example.
$registrySet = #()
$registryObj = New-Object -TypeName psobject
$registryObj | Add-Member -MemberType NoteProperty -Name Name -Value 'Toast'
$registryObj | Add-Member -MemberType NoteProperty -Name Key -Value 'HKLM:\SOFTWARE\Breakfast\'
$subKeySet = #()
$subKeyObj = New-Object -TypeName psobject
$subKeyObj | Add-Member -MemberType NoteProperty -Name Name -Value '1001'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Type -Value 'DWORD'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Value -Value '3'
$subKeySet += $subKeyObj
$subKeyObj = New-Object -TypeName psobject
$subKeyObj | Add-Member -MemberType NoteProperty -Name Name -Value '1002'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Type -Value 'DWORD'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Value -Value '3'
$subKeySet += $subKeyObj
$subKeyObj = New-Object -TypeName psobject
$subKeyObj | Add-Member -MemberType NoteProperty -Name Name -Value '1003'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Type -Value 'DWORD'
$subKeyObj | Add-Member -MemberType NoteProperty -Name Value -Value '1'
$subKeySet += $subKeyObj
$registryObj | Add-Member -MemberType NoteProperty -Name SubKeySet -Value
$subKeySet
$registrySet += $registryObj
$registrySet | Where {$_.Name -ieq 'toast'} | select SubKeySet
I have an array of information in Powershell that is comprised of objects within objects. It goes down 4 objects deep. Is there a way to select a property from the bottom most object while remaining at the top level? Does this make sense what I am asking?
$result
active : active
security : #{waf=; acls=}
sealloaction :
siteDualFactorSettings : #{enabled=False; version=0}
login_protect : #{enabled=False; url_patterns=System.Object[]}
performance_configuration : #{advanced_caching_rules=; acceleration_level=standard; cache300x=False; cache_headers=System.Object[]}
$result.security
waf acls
--- ----
#{rules=System.Object[]} #{rules=System.Object[]}
$result.security.waf
rules
-----
{#{action=api.threats.action.block_request; action_text=Block; id=api.threats.sql_injection; name=SQL Injection}, #{action=api.threats.action.alert; action_text=Alert Only;}
To produce a mixed main/nested level selection:
foreach on the main array
select of the nested stuff + calculated properties to reference the iterated main array element
$results | ForEach {
$r = $_
$r.security.waf.rules | select *, #{N='domain'; E={$r.domain}}
}
Or as a one-liner:
$results | %{ $r = $_; $r.security.waf.rules | select *, #{N='domain'; E={$r.domain}} }
$myArray | where { $_.x.y.z -eq 'something' }
which is functionally equivalent to
foreach ($object in $myArray)
{
if ($object.x.y.z -eq 'something')
{
$object # Writes $object to the output stream
}
}
The easier way should be with $result | select ${L='label';E={$_.security.waf}} then you drill down to where you want.
For example:
$obj1_child = New-Object -TypeName psobject
$obj1_child | add-member -Name obj1_child -MemberType NoteProperty -Value "obj1_child"
$obj1_child | add-member -Name value1 -MemberType NoteProperty -Value "child_value1"
$obj1 = New-Object -TypeName psobject
$obj1 | add-member -Name obj1 -MemberType NoteProperty -Value "obj1"
$obj1 | add-member -Name child -MemberType NoteProperty -Value $obj1_child
$obj2 = New-Object -TypeName psobject
$obj2 | add-member -Name value1 -MemberType NoteProperty -Value "value1"
$parentObj = New-Object -TypeName psobject
$parentObj | Add-Member -MemberType NoteProperty -name string -value "parentObject"
$parentObj | Add-Member -MemberType NoteProperty -name OBJ1 -value $obj1
$parentObj | Add-Member -MemberType NoteProperty -name OBJ2 -value $obj2
$parentObj | select string,obj2, #{L='value of obj1 child';E={$_.obj1.child.value1}}
I create a $parentObj inside there's a string, and two objects ($obj1,$obj2), and inside $obj1 there's another object ($obj1_child). Now I want the string from $parentObj and the last string from $obj1_child this should work:
$parentObj | select string, #{L='value of obj1 child';E={$_.obj1.child.value1}}
Output:
>> $parentObj | select string, #{L='value of obj1 child';E={$_.obj1.child.value1}}
string value of obj1 child
------ -------------------
parentObject child_value1
Not sure if it's what you want. but it was what I was looking for when landed here, so may help someone else.
Reference:
https://devblogs.microsoft.com/scripting/access-objects-inside-other-objects-in-powershell-pipeline/
Two ways to do the same thing
$x = [PSCustomObject]#{
active = 'active';
security = [PSCustomObject]#{
waf = [PSCustomObject]#{
rules = #(
[PSCustomObject]#{
action='api.threats.action.block_request';
action_text='Block';
id='api.threats.sql_injection';
name='SQL Injection'},
[PSCustomObject]#{
action='api.threats.action.alert';
action_text='Alert Only';}
)
}
acls = [PSCustomObject]#{
rules = #(
[PSCustomObject]#{
action='api.threats.action.block_request';
action_text='Block';
id='api.threats.sql_injection';
name='SQL Injection'},
[PSCustomObject]#{
action='api.threats.action.alert';
action_text='Alert Only';}
)
}
}
}
$x.security.waf.rules | select *, #{name='domain'; ex={'foo'}}
$x | select -ExpandProperty security | select -ExpandProperty waf | select -ExpandProperty rules | select *, #{name='domain'; ex={'foo'}}
I'm trying trying to get two properties from two separate commands and add them to a variable to be able to further evaluate.
I was told a custom object would work...
Clear-Host
Add-PSSnapin citrix* -ErrorAction SilentlyContinue
$DRSrvs = Get-XAServer drptsw00* | select -ExpandProperty servername
$hash = $null
$hash = #{}
foreach ($DR in $DRSrvs) {
$hash = New-Object PsObject -Property #{
servername = $DR
Logins = (Get-XALoadEvaluator -ServerName $DR).LoadEvaluatorName
}
}
A hashtable is for mapping (unique) keys to values. If you need to map different servernames to login names use a hashtable, otherwise use custom objects. Either way you need to handle the data structures correctly.
Hashtable:
$hash = #{}
foreach ($DR in $DRSrvs) {
$hash[$DR] = (Get-XALoadEvaluator -ServerName $DR).LoadEvaluatorName
}
Custom object list:
$list = foreach ($DR in $DRSrvs) {
New-Object PsObject -Property #{
servername = $DR
Logins = (Get-XALoadEvaluator -ServerName $DR).LoadEvaluatorName
}
}
Assigning something to a variable in a loop replaces the previous value in that variable with each iteration, leaving you with just the last value after the loop finishes.
I used this method and got a very clean output. Citrix SDK for Powershell if very funny and has lots of gotchas.
Clear-Host
Add-PSSnapin citrix* -ErrorAction SilentlyContinue
$OutputData = $null
$OutputData = #()
$Srvs = Get-XAServer Srv123* | Select-Object -ExpandProperty ServerName
$object = New-Object PSObject
Add-Member -InputObject $object -MemberType NoteProperty -Name Servername -Value ""
Add-Member -InputObject $object -MemberType NoteProperty -Name LoadEval -Value ""
foreach ($Srv in $Srvs) {
$servername= $Srv
$LoadEval = ((Get-XALoadEvaluator -ServerName $Srv).LoadEvaluatorName)
$appObject = New-Object System.Object
$appObject |
Add-Member -MemberType NoteProperty -Name "ServerName" -Value $servername -PassThru |
Add-Member -MemberType NoteProperty -Name "LoadEval" -Value $LoadEval
$outputData += $appObject
}
I am trying to sort out arrays in PS. The problem I am trying to solve is to return a list of replicated VMs and some basic stats.
Having read through a multitude of sites and suggestions the best I could get is the following script:
$myArray = #()
$vms = get-vm | where-object { $_.replicationstate -ne "Disabled" }
foreach ($vm in $vms)
{
$vmRepl = Get-VMReplication
$replfreq = (New-TimeSpan -seconds $vmRepl.replicationfrequencysec)
$lastrepl = $vmRepl.lastreplicationtime
$nextrepl = $lastrepl + $replfreq
$secfrom = [math]::Round((New-TimeSpan -start $vmRepl.lastreplicationtime).TotalSeconds)
$secto = [math]::Round((New-TimeSpan -end ($vmRepl.lastreplicationtime + $replfreq)).TotalSeconds)
$obj = New-Object System.Object
$obj | Add-Member -MemberType NoteProperty -Name Name -Value $vmRepl.Name
$obj | Add-Member -MemberType NoteProperty -Name ReplicationFrequencySec -Value $vmRepl.replicationfrequencysec
$obj | Add-Member -MemberType NoteProperty -Name SecondsSinceLastRepl -Value $secfrom
$obj | Add-Member -MemberType NoteProperty -Name SecondsUntilNextRepl -Value $secto
$obj | Add-Member -MemberType NoteProperty -Name LastReplication -Value $lastrepl
$obj | Add-Member -MemberType NoteProperty -Name NextReplication -Value $nextrepl
$myArray += $obj
}
write-output $myArray | ft -AutoSize
This works when I only have one VM, but when there are multiple ones the output appears within curly braces.
I think I am on the right track finally. I just need someone to help me sort out the remaining piece(s) of the puzzle.
The other weird thing is that the New-TimeSpan stops working with multiple VMs.
Thanks in advance.
Braden
The biggest probem with your script is : you start a foreach loop but you don't use any element from the array you're looping through. You just loop through the same data for each item in the array.
Basicly the current script retreives a list of VMs, then for each entry you fetch the replication status of all the machines in the array. Then you do some processing on this set and then add this set to a new object (and this goes on for each entry in your list). For a good explanation on the usage of foreach see
http://blogs.technet.com/b/heyscriptingguy/archive/2014/04/28/basics-of-powershell-looping-foreach.aspx
I would also suggest to use [PSCustomObject] instead of new-object / add-member : it's easier to use, the code is easier to read and it also maintains the order of the properties you set with it (since you're using get-vm I assume you have PS3 or higher)
I think you might be overwriting the same object ($obj) in each foreach() iteration.
Try this instead:
$VMs = Get-Vm | Where-Object {$_.ReplicationState -ne 'Disabled'}
$MyVmReplicationStatus = foreach ($VM in $VMs){
$VMReplStatus = Get-VMReplication
$LastRepTime = $VMReplStatus.LastReplicationTime
$ReplFreqSecs = $VMReplStatus.ReplicationFrequencySec
$ReplFrequency = (New-TimeSpan -Seconds $ReplFreqSecs)
$Props = #{
Name = $VMReplStatus.Name
ReplicationFrequencySec = $ReplFreqSecs
SecondsSinceLastRepl = [System.Math]::Round((New-TimeSpan -Start $LastRepTime).TotalSeconds)
SecondsUntilNextRepl = [System.Math]::Round((New-TimeSpan -End ($LastRepTime + $ReplFrequency)).TotalSeconds)
LastReplication = $LastRepTime
NextReplication = $LastRepTime + $ReplFrequency
}
New-Object -TypeName psobject -Property $Props
}
Write-Output -InputObject $MyVmReplicationStatus | Format-Table -AutoSize