Adding property with multiple values to a custom object - powershell

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!

Related

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.

Test a Large Number of Registry Values in 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

Get list of processes same as in task manager in powershell console

I'm trying to get same list of process as in Task Manager in Windows 2008 server
I can't get some values even from WMI objects, such as CPU time, UAC Virtualisation, User Name (e.g. process owner), User Objects, CPU Usage, all memory columns, Handles and threads.
Here is some piece of code I made trying to make it work
Clear-Host
$Processes = Get-Process
foreach ($Process in $Processes) {
$PIDN = $Process.Id
$NAMEProcess = $Process.Name
$NAME = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).Name
$PATH = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).Path
$CMD = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).CommandLine
$OWNER = (Get-WmiObject win32_process | where {$_.Name -match $NAMEProcess}).getowner().user
$SESSIONID = (Get-WmiObject Win32_Process | where {$_.Name -match $NAMEProcess}).SessionId
$CPU = $Process.CPU
$WORKINGSET64 = $Process.WorkingSet64
$PEAKWORKINGSET64 = $Process.PeakWorkingSet64
$THREADS = $Process.Threads.Count
$HANDLES = $Process.Handles
$DESCRIPTION = $Process.Description
$obj = new-object psobject
$obj | add-member noteproperty "PID" ($PIDN)
$obj | add-member noteproperty "NAME" ($NAME)
$obj | add-member noteproperty "OWNER" ($OWNER)
$obj | add-member noteproperty "PATH" ($PATH)
$obj | add-member noteproperty "Command Line" ($CMD)
$obj | Add-Member noteproperty "SessionID" ($SESSIONID)
$obj | Add-Member noteproperty "CPU" ($CPU)
$obj | Add-Member noteproperty "WorkingSet64" ($WORKINGSET64)
$obj | Add-Member noteproperty "Peak Working Set64" ($PEAKWORKINGSET64)
$obj | Add-Member noteproperty "HANDLES" ($HANDLES)
$obj | Add-Member noteproperty "THREADS" ($THREADS)
$obj | Add-Member noteproperty "DESCRIPTION" ($DESCRIPTION)
write-output $obj | Format-Table
# $obj | Format-Table $PIDN, $NAME
}
Also couldn't make it to output in proper table. Could you please help me with that? Thank you.
did you take a look at performance counters?
Get-Counter "\Process(*)\Working Set - Private"
Get-Counter "\Process(*)\Handle Count"
For further documentation on Get-Counter use
get-help get-counter -Full
or goto: https://technet.microsoft.com/en-us/library/hh849685.aspx
First, you need to be using the data from a single use of Get-WMIObject (gwmi).
When you make multiple calls using GWMI, you're effectively taking another snapshot of data every time and using a different sample for each property. You're going to end up with a table of data befitting of a cubist Picasso painting... it won't align or represent the whole list. It also takes a lot more time to recapture the process list over and over then grab only a single property from each different list, so its worth it to take the time to modify a single set of data, especially if you end up repurposing your script in a massive remote operations script that compiles a database of tasks.
There are different ways to grab only the properties you want in the form of PS Custom Objects. I use hash tables to make the code short and easy, and to make the code efficient--
You already executed the verbage you would have done for a hash table, in the form of:
$CPU = $Process.CPU
$WORKINGSET64 = $Process.WorkingSet64
$PEAKWORKINGSET64 = $Process.PeakWorkingSet64
$THREADS = $Process.Threads.Count
$HANDLES = $Process.Handles
$DESCRIPTION = $Process.Description
So instead, just do
$taskProps = #{
'SID'=$task.SessionId
'Name'=$task.ProcessName
'PID'=$task.ProcessId
# add more properties here.
}
And instead of creating a blank custom object, and then 'writing' to it multiple times with
$obj = new-object psobject
$obj | add-member noteproperty "PID" ($PIDN)
$obj | add-member noteproperty "NAME" ($NAME)
$obj | add-member noteproperty "OWNER" ($OWNER)
$obj | add-member noteproperty "PATH" ($PATH)
$obj | add-member noteproperty "Command Line" ($CMD)
$obj | Add-Member noteproperty "SessionID" ($SESSIONID)
$obj | Add-Member noteproperty "CPU" ($CPU)
$obj | Add-Member noteproperty "WorkingSet64" ($WORKINGSET64)
$obj | Add-Member noteproperty "Peak Working Set64" ($PEAKWORKINGSET64)
$obj | Add-Member noteproperty "HANDLES" ($HANDLES)
$obj | Add-Member noteproperty "THREADS" ($THREADS)
$obj | Add-Member noteproperty "DESCRIPTION" ($DESCRIPTION)
You can package the property hashtable and create in a single shot, the custom object at the end:
$taskObject = New-Object -TypeName PSObject -Property $taskProps
Then store it in an arraylist with
$taskList += $taskObject
You can see my example here:
# Generates a collection of "System.Management.ManagementObject#root\cimv2\Win32_Process"
# Only do this once. Every time gwmi is used, it makes another RPC call if used remotely.
# If you do multiple GWMIs you'll be working with differing data samples.
$taskSnapshot = Get-WMIObject -ComputerName [machine name] -Class Win32_Process
# Initialize, nullify, and declare your list as an empty ArrayList.
$taskList = #()
# Begin formatting in prep of Format-Table or similar usage
# This is where you'd define each property you want to see, manipulation, etc.
foreach ($task in $taskSnapshot){
# Create the hash table which will temporarily store all information for each task naming/assigning only
# properties you want to display.
$taskProps = #{
'SID'=$task.SessionId
'Name'=$task.ProcessName
'PID'=$task.ProcessId
# additional properties here.
}
# "Packages" the new custom object in a variable that stores the object
$taskObject = New-Object -TypeName PSObject -Property $taskProps
# append (addition) operation on formerly defined arraylist to store
# the packaged object to an arraylist.
$taskList += $taskObject
}
# Displays the list of task "objects" in a table, other formatting options are available online.
$taskList | Format-Table -AutoSize
Using Format Commands to Change Output View:
https://technet.microsoft.com/en-us/library/dd347677.aspx
Windows PowerShell: The Many Ways to a Custom Object:
https://technet.microsoft.com/en-us/magazine/hh750381.aspx
I would also recommend checking out Out-GridView as it will create a GUI table of the data which you can resize and easily click around in.
The important part is to use GWMI once. It would be better practice to capture the raw information in a single variable, then perform your Select-String/where/if/manipulation and formatting operations on the dataset within your foreach statement.
Here's the clean copy of the example, with my select aliases.
$taskSnapshot = gwmi -cn localhost -class win32_process
$taskList = #()
foreach ($task in $taskSnapshot){
$taskProps = #{
'SID'=$task.SessionId
'Name'=$task.ProcessName
'PID'=$task.ProcessId
}
$taskObject = New-Object -TypeName PSObject -Property $taskProps
$taskList += $taskObject
}
$taskList | Out-GridView
Another thing someone mentioned is the min/max working set properties...
You can look at all the properties of Win32_Process by doing
Get-WMIObject -Class Win32_Process | Get-Member
or
gwmi -cl win32_process | gm
To build on Chris Kuperstein answer:
I do not like that PowerShell rearranges my properties according to the default sort of the Keys collection in the HashTable; I put my properties in a distinct order for a reason. To counteract this you can utilize property sets. Worth noting that you absolutely could use Select-Object to achieve this too.
$taskObject = New-Object -TypeName PSObject -Property $taskProps
$taskObject.PSObject.TypeNames.Insert(0, "xTask")
$taskObject | Add-Member -MemberType ScriptMethod -Name "ToString" -Force -Value {
Write-Output "Name: $($this.Name) [$($this.PID)] - CPU: $($this.CPU)"
}
$defaultDisplaySet = #('SID', 'PID', 'Name', 'CPU', 'Threads', 'Handles', 'WorkingSet64', 'Owner', 'Description', 'Path')
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet("DefaultDisplayPropertySet",[string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]#($defaultDisplayPropertySet)
$taskObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

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
}

Why does this bizarre behavior with add-member on built-in types in Powershell happen?

Given this:
$x = new-object psobject
$x | add-member noteproperty test 'xtest'
$x.test
$x | add-member noteproperty test2 'xtest2'
$x.test2
The output is what I'd expect:
xtest
xtest2
But given this:
$y = #{}
$y | add-member noteproperty test 'ytest'
$y.test
$y | add-member noteproperty test2 'ytest2'
$y.test2
I simply get:
ytest2
I'm confused. And if I do this:
$y = #{}
$y | add-member noteproperty test 'ytest'
$y | add-member noteproperty test2 'ytest2'
$y.test
$y.test2
Then there is no output at all. Running through get-members confirms that the methods are not actually being added.
What's going on here? This has to be something dumb on my end, but I can't see it.
IIRC this really has to do with PSObject wrappers which is a key piece of the extended type system in PowerShell 2.0. When you execute this:
$x = new-object psobject
$x | add-member noteproperty test 'xtest'
$x.test
It works because the object is already a PSOject so the Add-Member can add the new NoteProperty directly to the PSObject e.g.:
$y = #{}
$y | add-member noteproperty test 'ytest'
$y.test
This doesn't work because $y isn't initially wrapped so when you execute Add-Member, it creates a new object that wraps the original hashtable. You can see this by using Get-Member e.g.:
$y | Get-Member
You won't see your test property. To get this to work in v2, you have to do this:
$y = $y | add-member noteproperty test ytest -passthru
$y.test
ytest
FYI, this changes in V3 since it is based on the DLR it modifies the object directly without creating a new wrapper object e.g.:
# PowerShell V3 only
16# $y = #{}
17# Add-Member -InputObject $y test ytest
18# $y.test
ytest
This is due to power shell unrolling your list which is empty. If you want to add a property to the list use the inputobject paramater
add-member -input $y noteproperty test1 'ytest1'
the usage you described in the question would add a member to every item in the list which you can see in this example
C:(...)SequenceId>$y = #()
C:(...)SequenceId>$y += New-Object -t psobject
C:(...)SequenceId>$y += New-Object -t psobject
C:(...)SequenceId>$Y | Add-Member -type noteproperty -name hi -value you
C:(...)SequenceId>$Y
hi
--
you
you
#Joey raised this issue that I used a array which is directly iterable. To get the OP semantic to do what I said you would have to do the folowing
C:(...)SequenceId>$y = #{}
C:(...)SequenceId>$y["one"] = New-Object -t psobject
C:(...)SequenceId>$y["two"] = New-Object -t psobject
C:(...)SequenceId>$Y | Add-Member -type noteproperty -name hi -value you
C:(...)SequenceId>$y
Name Value
---- -----
two
one
C:(...)SequenceId>$Y.values | Add-Member -type noteproperty -name hi -value you
C:(...)SequenceId>$y
Name Value
---- -----
two #{hi=you}
one #{hi=you}