How to get all instances of a class? - powershell

My knowledge of classes is relatively new. But I want to output all objects/instances of a class with powershell. Is this even possible? Here is an example of how I create two objects of the class Computer.
Class Computer {
[String]$Name
[String]$Description
[String]$Type
}
$NewComputer = New-Object 'Computer'
$NewComputer.Name = 'ultra1'
$NewComputer.Description = 'Lenovo Yoga 900'
$NewComputer.Type = 'Ultrabook'
$NewComputer = New-Object 'Computer'
$NewComputer.Name = 'ultra2'
$NewComputer.Description = 'Lenovo Yoga X1'
$NewComputer.Type = 'Ultrabook'
Now I want to output both objects, how can I do this?

Judging from your comment "if there is a possibility to get the objects of a class without putting them into a collection", I think what you want to do is to create new Computer objects using your class and later on use these objects as separate variables.
For easier creation, I'd suggest you add a constructor to the class, so you can create the objects in a single line:
Class Computer {
[String]$Name
[String]$Description
[String]$Type
# add a constructor
Computer(
[string]$n,
[string]$d,
[string]$t
){
$this.Name = $n
$this.Description = $d
$this.Type = $t
}
}
# now create the computer objects
[Computer]$pcUltra1 = [Computer]::new('ultra1','Lenovo Yoga 900','Ultrabook')
[Computer]$pcUltra2 = [Computer]::new('ultra2','Lenovo Yoga X1','Ultrabook')
# show what you have now
$pcUltra1
$pcUltra2
Output:
Name Description Type
---- ----------- ----
ultra1 Lenovo Yoga 900 Ultrabook
ultra2 Lenovo Yoga X1 Ultrabook
Hope that helps

Maybe this will help
Class Computer {
[String]$Name
[String]$Description
[String]$Type
}
# a collection of computers
$computers =#()
$NewComputer = New-Object 'Computer'
$NewComputer.Name = ‘ultra1’
$NewComputer.Description = ‘Lenovo Yoga 900’
$NewComputer.Type = ‘Ultrabook’
# append a computer to teh collection
$computers += $NewComputer
$NewComputer = New-Object 'Computer'
$NewComputer.Name = ‘ultra2’
$NewComputer.Description = ‘Lenovo Yoga X1’
$NewComputer.Type = ‘Ultrabook’
# append a computer to teh collection
$computers += $NewComputer
# this outputs each of the computers
$computers
# or you can format the data in a table
$computers | Format-Table -AutoSize
# or in a list
$computers | Format-List *
# or as json
$computers | ConvertTo-Json

Assuming you haven't re-bound any names, you can do the following:
Get-Variable | Select-Object -ExpandProperty Value |
Where-Object { $_.GetType().Name -eq 'Computer' }

Related

Possible to pull info from AdditionalProperties dictionary with Microsoft Graph PowerShell cmdlets?

I am trying to use PowerShell Graph cmdlets instead of the Azure AD module cmdlets. With the Azure AD module, I can do this:
# This is what I want:
get-azureadgroupmember -objectid $GroupID | select-object -property displayname, `
mail, userprincipalname, objectid
DisplayName Mail UserPrincipalName ObjectId
----------- ---- ----------------- --------
John Smith John.Smith#example.org jsmith#example.org 4bae8291-6ec3-192b-32ce-dd21869ef784
(...)
# All of these properties are directly accessible in the returned objects:
$res = get-azureadgroupmember -objectid $GroupID
$res[0] | fl -prop *
# Shows long list of directly accessible properties
I'm trying to figure out the equivalent with PowerShell Graph:
$res = get-mggroupmember -groupid $GroupID
$res[0] | fl -prop *
# Only properties are DeletedDateTime, Id, and AdditionalProperties
# Want to do something like this, but it doesn't work:
get-mggroupmember -groupid $GroupID | select-object -property id, `
additionalproperties['displayName'], additionalproperties['mail'], `
additionalproperties['userPrincipalName']
# This works, but is there a better option???
get-mggroupmember -groupid $GroupID | foreach-object { `
"{0},{1},{2},{3}" -f $_.id, $_.additionalproperties['displayName'], `
$_.additionalproperties['mail'], $_.additionalproperties['userPrincipalName']
}
AdditionalProperties is a dictionary (IDictionary) which contains displayname, mail, and userprincipalname. My thought is there is probably a better way to do this or to get at the information.
There are a few interesting parameters in get-mggroupmember that I'm not clear on including "-expandproperty" and "-property". I've tried playing around with these but haven't had any luck. I'm wondering if there's a way to use these to do what I want.
Suggestions?
Given the following $object, 3 properties and one of them AdditionalProperties is a Dictionary<TKey,TValue>:
$dict = [Collections.Generic.Dictionary[object, object]]::new()
$dict.Add('displayName', 'placeholder')
$dict.Add('mail', 'placeholder')
$dict.Add('userPrincipalName', 'placeholder')
$object = [pscustomobject]#{
DeletedDateTime = 'placeholder'
Id = 'placeholder'
AdditionalProperties = $dict
}
Supposing from this object you're interested in Id, displayName and mail, you could use Select-Object with calculated properties:
$object | Select-Object #(
'Id'
#{
Name = 'displayName'
Expression = { $_.additionalProperties['displayName'] }
}
#{
Name = 'mail'
Expression = { $_.additionalProperties['mail'] }
}
)
However this gets messy as soon as you need to pick more property values from the objects, PSCustomObject with a loop comes in handy in this case:
$object | ForEach-Object {
[pscustomobject]#{
Id = $_.Id
displayName = $_.additionalProperties['displayName']
mail = $_.additionalProperties['mail']
}
}
Both alternatives would output the same "flattened" object that can be converted to Csv without any issue:
As Object
Id displayName mail
-- ----------- ----
placeholder placeholder placeholder
As Csv
"Id","displayName","mail"
"placeholder","placeholder","placeholder"
In that sense, you could construct an array of objects using one of the above techniques, for example:
Get-MgGroupMember -GroupId $GroupID | ForEach-Object {
[pscustomobject]#{
Id = $_.id
displayName = $_.additionalproperties['displayName']
mail = $_.additionalproperties['mail']
userPrincipalName = $_.additionalproperties['userPrincipalName']
}
}
If you're looking for a programmatical way to flatten the object, you can start by using this example, however it's important to note that this can only handle an object which's property is nested only once, in other words, it can't handle recursion:
$newObject = [ordered]#{}
foreach($property in $object.PSObject.Properties) {
if($property.Value -is [Collections.IDictionary]) {
foreach($addproperty in $property.Value.GetEnumerator()) {
$newObject[$addproperty.Key] = $addproperty.Value
}
continue
}
$newObject[$property.Name] = $property.Value
}
[pscustomobject] $newObject
The output from this would become a flattened object like this, which also, can be converted to Csv without any issue:
DeletedDateTime : placeholder
Id : placeholder
displayName : placeholder
mail : placeholder
userPrincipalName : placeholder
It's also worth noting that above example is not handling possible key collision, if there are 2 or more properties with the same name, one would override the others.
Bonus function that should work with the objects returned by the cmdlets from Graph, AzureAD and Az Modules. This function can be useful to flatten their Dictionary`2 property. It only looks one level deep if the property value implements IDictionary so don't expect it to flatten any object. For the given example should work well.
function Select-GraphObject {
[CmdletBinding()]
param(
[parameter(ValueFromPipeline, DontShow)]
[object] $InputObject,
[parameter(Position = 0)]
[string[]] $Properties = '*'
)
begin {
$firstObject = $true
$toSelect = [Collections.Generic.List[object]]::new()
}
process {
if($firstObject) {
foreach($property in $InputObject.PSObject.Properties) {
foreach($item in $Properties) {
if($property.Value -is [Collections.IDictionary]) {
foreach($key in $property.Value.PSBase.Keys) {
if($key -like $item -and $key -notin $toSelect.Name) {
$toSelect.Add(#{
$key = { $_.($property.Name)[$key] }
})
}
}
continue
}
if($property.Name -like $item -and $property.Name -notin $toSelect) {
$toSelect.Add($property.Name)
}
}
}
$firstObject = $false
}
$out = [ordered]#{}
foreach($item in $toSelect) {
if($item -isnot [hashtable]) {
$out[$item] = $InputObject.$item
continue
}
$enum = $item.GetEnumerator()
if($enum.MoveNext()) {
$out[$enum.Current.Key] = $InputObject | & $enum.Current.Value
}
}
[pscustomobject] $out
}
}
Using copies of the $object from above examples, if using the default value of -Properties, the example objects would be flattened:
PS /> $object, $object, $object | Select-GraphObject
DeletedDateTime Id displayName mail userPrincipalName
--------------- -- ----------- ---- -----------------
placeholder placeholder placeholder placeholder placeholder
placeholder placeholder placeholder placeholder placeholder
placeholder placeholder placeholder placeholder placeholder
Or we can filter for specific properties, even Keys from the AdditionalProperties Property:
PS /> $object, $object, $object | Select-GraphObject Id, disp*, user*
Id displayName userPrincipalName
-- ----------- -----------------
placeholder placeholder placeholder
placeholder placeholder placeholder
placeholder placeholder placeholder

PowerShell variable definition from a pscustomobject

i've got this piece of code from a script i found on the web (just showing the part that interests me)
ForEach ($Computer in $Computername) {
$adsi = [ADSI]"WinNT://$Computername"
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
[pscustomobject]#{
UserName = $_.Name[0]
SID = ConvertTo-SID -BinarySID $_.ObjectSID[0]
PasswordAge = [math]::Round($_.PasswordAge[0]/86400)
LastLogin = If ($_.LastLogin[0] -is [datetime]){$_.LastLogin[0]}Else{'Never logged on'}
UserFlags = Convert-UserFlag -UserFlag $_.UserFlags[0]
MinPasswordLength = $_.MinPasswordLength[0]
MinPasswordAge = [math]::Round($_.MinPasswordAge[0]/86400)
MaxPasswordAge = [math]::Round($_.MaxPasswordAge[0]/86400)
BadPasswordAttempts = $_.BadPasswordAttempts[0]
MaxBadPasswords = $_.MaxBadPasswordsAllowed[0]
}
}
}
the code displays things on the console, but i would like to define/use these values as variables instead (as i want to use them in a hash table afterwards to send them in a http/POST request afterwards)
is there a way to get all these attributes as variables such as $LastLogin, $MinPasswordAge etc ?
as i don't want to display them, but send them in a POST like this :
$postParams = #{LastLogin=$LastLogin;MinPasswordAge=$MinPasswordAge}
Invoke-WebRequest -Uri http://example.com/foobar -Method POST -Body $postParams
to be honest i'm a complete newbie in PowerShell (i'm a Perl guru) and i don't know what pscustomobject does in there, i just want to define the variables in that loop, and use them at the end.
i've tried a couple of things with no success (can post them if required)
thanks !
Your own solution works, but only if you perform all processing inside the ForEach-Object script block (unless there's only ever 1 iteration, which doesn't appear to be the case here).
If you want to process the results later, you can simply collect them in an array by assigning the entire foreach loop to a variable (code shortened):
$allUsers = foreach ($Computer in $Computername) {
$adsi = [ADSI]"WinNT://$Computername"
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
# Output a custom object for each user.
[pscustomobject]#{
ComputerName = $Computer # also record the computer name
UserName = $_.Name[0]
SID = ConvertTo-SID -BinarySID $_.ObjectSID[0]
# ...
}
}
}
You can then simply enumerate the collected [pscustomobject]s and access their properties rather than using variables:
foreach ($user in $allUsers) {
# Use the properties to define a hashtable for later use in a http/POST request.
$ht = #{
User = $user.UserName
# ...
}
}
nm,
i found the solution a minute ago.
just got rid of that pscustomobject hash completely, and assigning the variables directory
$adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
$UserName = $_.Name[0]
$SID = ConvertTo-SID -BinarySID $_.ObjectSID[0]
$PasswordAge = [math]::Round($_.PasswordAge[0]/86400)
$LastLogin = If ($_.LastLogin[0] -is [datetime]){$_.LastLogin[0]}Else{'Never logged on'}
$UserFlags = Convert-UserFlag -UserFlag $_.UserFlags[0]
$MinPasswordLength = $_.MinPasswordLength[0]
$MinPasswordAge = [math]::Round($_.MinPasswordAge[0]/86400)
$MaxPasswordAge = [math]::Round($_.MaxPasswordAge[0]/86400)
$BadPasswordAttempts = $_.BadPasswordAttempts[0]
$MaxBadPasswords = $_.MaxBadPasswordsAllowed[0]
Write-Host $UserName
}
}

Creating a dynamic hashtable in Powershell

I want to create an overview of the local computer in Powershell and output it in JSON via a hash table. Now this can have several hard disks and it must be created dynamically in the hash table.
My Code:
$name = (Get-WmiObject -Class Win32_ComputerSystem -Property
Name).Name #{foreach ($Disk in $Disk) { $stats.Add("$platten", $Disk[0].VolumeName) }
stats = #{ $name= #{
CPUusage = $CPU
RAMusage = $ram
disknames = $disknames[1]
SSDsum = $ssdsum
HDDsum = $hddsum
Disksum = $disksum
}
$disk1 = #{
}
$disk2 = #{
}
$disk3 = #{
}
}}
Now I ask the hard drives and saves them in an Hash table. Then the foreach loop should go through each disk and enter the data into the other hash table.
And here comes the Error, i try to put it into the Hashtable and it did not works..
Your question is very unclear end incomplete. However, I think this might help you on your way:
$ComuterSystem = Get-CimInstance -ClassName Win32_ComputerSystem
$Result = foreach ($Computer in $ComuterSystem) {
$LogicalDisk = Get-CimInstance -ClassName win32_logicaldisk -ComputerName $ComuterSystem.Name
# Create a new hashtable for each computer
$diskHash = #{}
# Foreach disk of that computer add it to the hashtable
foreach ($disk in ($LogicalDisk.Where({$_.DeviceID}))) {
$diskHash.Add($disk.DeviceID, $disk.Size)
}
[PSCustomObject]#{
Name = $Computer.Name
Model = $Computer.Model
Manufacturer = $Computer.Manufacturer
# Easiest is to simply store all data:
LogicalDisk = $LogicalDisk
# Or store the hashtable with your key value pair
Disks = $diskHash
# Or store a selection of what you need
Selection = $LogicalDisk | Select-Object DeviceID, VolumeName, Size, FreeSpace
}
}
$Result
$Result.Disks
$Result.LogicalDisk
$Result.Selection
if you create a hashtable it is typically of fixed size
initialize the variable $disknames like:
$disknames = New-Object System.Collections.ArrayList
then you can add entries like you tried to

Powershell output not formatting properly

I'm using this script to get some basic info from virtual machines on our HyperV cluster:
#Establish global variables and MasterList array
$VMList = Get-VM
$MasterList = #()
#Loop through VMs and get Name, Processor count, assigned memory, add to MasterList
foreach($vm in $VMList) {
$ALLVHD = Get-VHD $vm.harddrives.path -ComputerName $vm.computername
$MyObject = New-Object PSObject -Property #{
Name = ($vm).VMName
ProcessorCount = (Get-VMProcessor $vm).Count
AssignedMemory = ($vm).MemoryAssigned
DiskType = $VHD.VhdType
'Total(GB)' = [math]::Round($VHD.Size/1GB)
'Used(GB)' = [math]::Round($VHD.FileSize/1GB)
'Free(GB)' = [math]::Round($VHD.Size/1GB- $VHD.FileSize/1GB)
}
$MasterList += $MyObject
}
$MasterList | Out-GridView
It mostly works, but there are several problems. The column order is wrong, it outputs DiskType,Name,AssignedMemory,Free(GB),ProcessorCount,Used(GB),Total(GB) and I have no idea why because that's now how it's ordered in the code. Also, the Free,Used, and Total amounts are 71, 29, and 100 for all items when that is incorrect.
If any Powershell experts can help me with this, it would be much appreciated.
I figured it out, thanks for the suggestions
#Establish global variables and MasterList array
$VMList = Get-VM
$MasterList = #()
#Loop through all VMs on node
foreach($vm in $VMList) {
$ALLVHD = Get-VHD $vm.HardDrives.path -ComputerName $vm.computername
foreach($VHD in $ALLVHD){
$MyObject = New-Object PSObject -Property #{
Name = $vm.Name
DiskType = $VHD.VhdType
Path = $VHD.Path
'Total(GB)' = [math]::Round($VHD.Size/1GB)
'Used(GB)' = [math]::Round($VHD.FileSize/1GB)
'Free(GB)' = [math]::Round($VHD.Size/1GB- $VHD.FileSize/1GB)
ProcessorCount = (Get-VMProcessor $vm).Count
AssignedMemory = ($vm).MemoryAssigned
}
#Add information to MasterList array
$Masterlist += $MyObject
}
}
#Change this line to print output however you want
$MasterList | select Name,DiskType,Path,'Total(GB)','Used(GB)','Free(GB)',ProcessorCount,#{Expression={$_.AssignedMemory/1GB};Label="AssignedMemory(GB)"}

Find matches in two different Powershell objects based on one property

I am trying to find the matching names in two different types of Powershell objects
$Object1 has two properties - Name (string), ResourceID (uint32)
$object2 has one noteproperty - Name (system.string)
This gives me a list of the matching names but I also want the corresponding resourceID property from $object1.
$computers = Compare-Object $Object1.name $WSD_CM12 | where {$_.sideindicator -eq "=>"} | foreach {$_.inputobject}
These are big objects with over 10,000 items so I'm looking for the most efficient way to accomplish this.
If I'm understanding what you're after, I'd start by creating a hash table from your Object1 collection:
$object1_hash = #{}
Foreach ($object1 in $object1_coll)
{ $object1_hash[$object1.Name] = $object1.ResourceID }
Then you can find the ResourceID for any given Object2.name with:
$object1_hash[$Object2.Name]
Test bed for creating hash table:
$object1_coll = $(
New-Object PSObject -Property #{Name = 'Name1';ResourceID = 001}
New-Object PSObject -Property #{Name = 'Name2';ResourceID = 002}
)
$object1_hash = #{}
Foreach ($object1 in $object1_coll)
{ $object1_hash[$object1.Name] = $object1.ResourceID }
$object1_hash
Name Value
---- -----
Name2 2
Name1 1
Alternative method:
# Create sample list of objects with both Name and Serial
$obj1 = New-Object -Type PSCustomObject -Property:#{ Name = "Foo"; Serial = "1234" }
$obj2 = New-Object -Type PSCustomObject -Property:#{ Name = "Cow"; Serial = "4242" }
$collection1 = #($obj1, $obj2)
# Create subset of items with only Name
$objA = New-Object -Type PSCustomObject -Property:#{ Name = "Foo"; }
$collection2 = #($objA)
#Everything above this line is just to make sample data
# replace $collection1 and $collection2 with $Object1, $WSD_CM12
# Combine into one list
($collection1 + $collection2) |
# Group by name property
Group-Object -Property Name |
# I only want items that exist in both
Where { $_.Count -gt 1 } |
# Now give me the object
Select -Expand Group |
# And get the properties
Where { $_.Serial -ne $null }