Test a Large Number of Registry Values in Powershell - powershell

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

Related

How to export file properties in csv using powershell

I wanted to export a csv-File with File-Properties from some tif-Files.
With this command
Get-ChildItem -Recurse C:\tifs\ |
ForEach-Object {$_ | add-member -name "Owner" -membertype noteproperty `
-value (get-acl $_.fullname).owner -passthru} | Sort-Object fullname |
Select FullName,CreationTime,LastWriteTime,Length,Dimensions |
Export-Csv -Force -NoTypeInformation C:\Test\export.csv
I can export a csv just fine. But as soon as I want to add properties like vertical resolution it fails. I don't quite understand why.
In order to get to the "extended" file properties (like Dimension and Resolution metadata) you have to resort to using the Windows Visual Basic shell options from inside PowerShell as Steven helpfully pointed out. Here is a code sample that should give you the result:
$files = #()
$folder = (New-Object -ComObject Shell.Application).namespace("C:\tifs")
# Loop through each file in folder
foreach ($f in $folder.Items()) {
$a = 0
# Print all the available properties (for debugging purposes)
for ($a ; $a -le 266; $a++) {
if($folder.GetDetailsOf($f, $a)) {
Write-Host "Property: $($folder.GetDetailsOf($folder.items, $a))"
Write-Host "Value: $($folder.GetDetailsOf($f, $a))"
Write-Host "Index: $($a)"
}
}
# Store data in custom PowerShell object
$obj = New-Object -TypeName PSOBJECT
# Fill each property with the file metadata (by index number)
$obj | Add-Member -MemberType NoteProperty -Name FullName -Value $folder.GetDetailsOf($f, 194)
$obj | Add-Member -MemberType NoteProperty -Name CreationTime -Value $folder.GetDetailsOf($f, 4)
$obj | Add-Member -MemberType NoteProperty -Name LastWriteTime -Value $folder.GetDetailsOf($f, 5)
$obj | Add-Member -MemberType NoteProperty -Name Length -Value $folder.GetDetailsOf($f, 1)
$obj | Add-Member -MemberType NoteProperty -Name Dimensions -Value $folder.GetDetailsOf($f, 31)
# Add custom object to a collection
$files += $obj
}
# Export collection to CSV
$files | Export-Csv -Force C:\Test\export.csv -NoTypeInformation -Encoding UTF8
From what I can tell there's no obvious PowerShell/.Net approach to getting additional file meta data. However. there are some COM based approaches.
Check Scripting Guys
And they reference this
You will still have to correlate the data. I usually do that by building hash table keyed of same values, in you can index the metadata using the path property, then use the FullName property of the file info objects to reference it, so you can the properties.

Export-Csv doesn't work as I expected

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.

Adding property with multiple values to a custom object

I have a custom object where I have added properties of a variable to it directly but there is one property with holds multiple values . How do I add all those multiple values to my custom object ?
This works fine if there is one-one value
$info | Add-Member -Type NoteProperty -Name "USEDSPACE %" -Value $usage
but in this case $result.name isnt working , where $result.name has a set of values not one
$info | Add-Member -Type NoteProperty -Name PATHS -Value $result.name
This is how I managed to add multiple values to a PS custom object, maybe you can adapt it to your needs:
$table = New-Object psobject
$table | gm
$keys = New-Object System.Collections.ArrayList
for([int32]$i = 1;$i -le 8;$i++){[void]$keys.add($i)}
$table | Add-Member -MemberType NoteProperty -Name Keys -Value $keys
check your data and type:
$table | gm
$table
$table.Keys
$table.Keys[0]
$table.Keys[5]
You can use of course any data type you need, in my case an int32 was needed, but you can add anything to your list, and the noteproperty of your custom object will have the same data type.
If you want to use the values in your object, you can access them like this, and do something with them.
foreach($key in $table.Keys){[math]::Pow(2,$key)}
Hope I could help!

Selecting objects from a Custom Object

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
}

How do I create a custom array in powershell?

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