Powershell Multidimensional associative table - powershell

Greatings, I'm trying to extract WMI informations with Powershell in order to push them in a Mysql DB via PHP.
If a machine has no access to the internet, I export an XML file (that is working).
$filePath = $PSScriptRoot + '\processors.xml'
$XmlWriter = New-Object System.XMl.XmlTextWriter($filePath,$Null)
$processor = Get-WmiObject Win32_Processor
$xmlWriter.WriteStartElement("Processor")
foreach ($item in $processor){
$xmlWriter.WriteStartElement($item.DeviceID)
$xmlWriter.WriteElementString("Name",$item.Name)
$xmlWriter.WriteElementString("AddressWidth",$item.AddressWidth)
$xmlWriter.WriteElementString("NumberOfCores",$item.NumberOfCores)
$xmlWriter.WriteElementString("NumberOfLogicalProcessors",$item.NumberOfLogicalProcessors)
$xmlWriter.WriteElementString("L2CacheSize",$item.L2CacheSize)
$xmlWriter.WriteElementString("L3CacheSize",$item.L3CacheSize)
$xmlWriter.WriteEndElement()
}
$xmlWriter.WriteEndElement()
$xmlWriter.Finalize
$xmlWriter.Flush()
$xmlWriter.Close()
But if a machine has access to the internet, I prefer to push informations directly to a page, via HTTP POST, which will verify informations and store them in the DB.
Here is my problem, as I don't know the number of items (here processors but imagine with memories, hard drives... etc.) I use a foreach statement to create the XML file (and it works) but, I don't know how to create the equivalent structure to create and fill a multi dimensionnal arry $postprocessor that stores CPUs informations. I have tried the following code but I'm stuck (that is not working) :
$processor = Get-WmiObject Win32_Processor
$postprocessor = #()
foreach ($item in $processor){
$itemarr = #()
$itemarr += 'Name="'+$item.Name+'"'
$itemarr += 'AddressWidth="'+$item.AddressWidth+'"'
$itemarr += 'NumberOfCores="'+$item.NumberOfCores+'"'
$itemarr += 'NumberOfLogicalProcessors="'+$item.NumberOfLogicalProcessors+'"'
$itemarr += 'L2CacheSize="'+$item.L2CacheSize+'"'
$itemarr += 'L3CacheSize="'+$item.L3CacheSize+'"'
$postprocessor.Add($item.DeviceID+'='+$itemarr)
# write-host $itemarr
Remove-Variable itemarr
}
$postParams = $postprocessor
$sending = Invoke-WebRequest -Uri http://localhost:8080/wmi/test.php -Method POST -Body $postParams
In fact I just wanted to store those informations like this :
$postParam[]
$postProcessors[]
->Processor1[]
->Name = value
...
->L3Cachesize = value
->Processor2[]
->Name = value
...
->L3Cachesize = value
postMemories[...]
postHardDrives[...]
etc
Maybe I have not found previous answer that solve my problem, if it is the case excuse me by advance and I'll be glad to read as many links that you'll send to me.
Best

What you'd probably need is a dictionary and not an array of arrays.
$processor = Get-WmiObject Win32_Processor
$postprocessor = #{}
foreach ($item in $processor){
$itemHash = #{
Name = $item.Name
AddressWidth = $item.AddressWidth
NumberOfCores = $item.NumberOfCores
NumberOfLogicalProcessors = $item.NumberOfLogicalProcessors
L2CacheSize = $item.L2CacheSize
L3CacheSize = $item.L3CacheSize
}
$postprocessor.Add($item.DeviceID, $itemHash)
}
$postParams = $postprocessor
This is what I see in the output.
PS C:\> $postprocessor
Name Value
---- -----
CPU0 {Name, NumberOfCores, AddressWidth, NumberOfLogicalProcessors...}
PS C:\> $postprocessor["cpu0"]
Name Value
---- -----
Name Intel(R) Core(TM) i7-3630QM CPU # 2.40GHz
NumberOfCores 4
AddressWidth 64
NumberOfLogicalProcessors 8
L2CacheSize 1024
L3CacheSize 6144
PS C:\> ConvertTo-Json -InputObject $postprocessor
{
"CPU0": {
"Name": "Intel(R) Core(TM) i7-3630QM CPU # 2.40GHz",
"NumberOfCores": 4,
"AddressWidth": 64,
"NumberOfLogicalProcessors": 8,
"L2CacheSize": 1024,
"L3CacheSize": 6144
}
}

Related

How to get all instances of a class?

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' }

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)"}

Adding objects to array to create multiple columns

I'm trying to get the DisplayName of all Software listed in AddRemovePrograms for each computer, add it to an array under the name of the computer, then export. Here's what I have:
$Computers = gc "C:\Get Software.txt"
$CSV = "C:\Get Software.csv"
$Results = #()
If (Test-Path $CSV) {
Remove-Item $CSV
}
Foreach($Computer in $Computers){
#Get DisplayName of Software Installed on Asset
$Software = Get-WmiObject Win32Reg_AddRemovePrograms -ComputerName $Computer | Select-Object -ExpandProperty DisplayName
$counter = 0
While ($counter -lt $Software.count){
#Create a PSObject. Loops through all software and adds to $Results.
$Obj = New-Object PSOBJECT
Add-member –inputobject $Obj –membertype Noteproperty -Name $Computer -Value $Software[$counter]
$counter++
$Results+=$Obj
}
}
$Results | Export-Csv $CSV -NoTypeInformation
Unfortunately, the output only lists the first Computer in the CSV. I've tried stepping through this to understand it, I just don't understand why I can't add the $Obj variable to $Results with a different Name Property (In this case I'm looping and making a new name based on the computer name in my .txt file). It seems to only take the first input and won't put a new column header then spit out the software. Below is an example of what I'm getting and what I'd like to get instead.
OUTPUT
A01234
Program1
Program2
Program3
EXPECTED OUTPUT
A01234 B05678 C09123
Program1 Program97 Program30
Program2 Program98 Program31
Program3 Program99 Program32
Hopefully this makes some sense. Any assistance would be appreciated, I'm clearly doing something wrong with the objects and I'm not seeing it. Thanks!
This is do-able, it just isn't quite as simple as you would think. What I would suggest is setting things up as a hashtable where the keys are your computer names and your values are an array of software titles for each computer. Then you find out how many titles the computer with the most software has (that's how many rows you'll have, so it's how many times you'll have to loop), and create a loop to make that many objects. Each object will have no properties to start, and then we add a property for each computer in the hashtable, and the value for that property will be the Nth item listed in the hashtable for that computer. Here's the code, it might make this all make more sense:
$Computers = 'A01234','B05678','C09123'
$SftwrPerCmp = [ordered]#{}
ForEach($Computer in $Computers){
$SftwrPerCmp.add($Computer,#())
#Randomly generate 7-15 strings as 'Programs' for current computer
$Software = 1..$(get-random -Maximum 15 -Minimum 7)|%{(Get-Random -inputobject $("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -split ''|?{$_}) -count 6) -join ''}
ForEach($Title in $Software){
$SftwrPerCmp["$Computer"] += $Title
}
}
$MaxTitleCount = $SftwrPerCmp.Values|%{$_.count}|sort|select -last 1
$Results = #()
For($i=0;$i -lt $MaxTitleCount;$i++){
$Record = New-Object PSObject
$SftwrPerCmp.Keys | ForEach{Add-Member -InputObject $Record -NotePropertyName $_ -NotePropertyValue $SftwrPerCmp["$_"][$i]}
$Results += $Record
Clear-Variable Record
}
$Results|Format-Table -AutoSize
Now obviously you will not randomly generate software titles, you'll use your existing $Software = Get-WmiObject line that's in your existing code instead since that part was at least working for you. This should result in the listings that you were looking for. My code resulted in this:
A01234 B05678 C09123
------ ------ ------
CU7K5E 6GJWOB 97H1TY
7VCZ5T CIPWVK 760NKU
CHKPY0 J4B7D0 1QOSD3
2YEFR4 2VY6DM O68SKU
VI7ZQG WLJQN9 Q5VJAZ
ZQOKNV R9KZG1 H2XZK4
S8IZC4 GRSMPU BIZXKA
LAVNI0 TKBOUC K9DEFU
3U7KVO JZ3X4H
A6GVUK 18AC5H
NMI32Q H14GPJ
50KSZ6 XU0FWC
PAN5TC 9WXR5U
531M04

Powershell script: create loop for ResponseTime

I am having an issue with the way that my ping results "roll" out on the screen. I am using this code:
$servers = "192.168.2.10","192.168.2.80","192.168.2.254"
$collection = $()
foreach ($server in $servers)
{
$status = #{ "ServerName" = $server; "TimeStamp" = (Get-Date -f s) }
$testconnection = (Test-Connection $server -Count 1 -ea 0)
$response = ($testconnection | select ResponseTime)
if ($response)
{
$status["Results"] = "Up"
$status["Responsetime"] = $response
}
else
{
$status["Results"] = "Down"
}
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus
$collection += $serverStatus
}
$collection | Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
I would to like create a loop for the ResponseTime
The code that I am using now gives one response.
When I give a count of 2, it prints the ResponseTime next to eachother per IP-adres.
Output:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:30:17 {#{ResponseTime=6}, #{ResponseTime=4}} Up 192.168.2.10
2014-10-22T23:30:18 Down 192.168.2.80
2014-10-22T23:30:25 {#{ResponseTime=1}, #{ResponseTime=3}} Up 192.168.2.254
What I want is, that the script prints each ResponseTime under eachother like this:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:11:50 #{ResponseTime=419} Up 192.168.2.10
2014-10-22T23:11:51 #{ResponseTime=415} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:52 #{ResponseTime=470} Up 192.168.2.254
2014-10-22T23:11:52 #{ResponseTime=7} Up 192.168.2.254
Or like this:
TimeStamp Responsetime Results ServerName
--------- ------------ ------- ----------
2014-10-22T23:11:50 #{ResponseTime=419} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:51 #{ResponseTime=415} Up 192.168.2.254
2014-10-22T23:11:52 #{ResponseTime=470} Up 192.168.2.10
2014-10-22T23:11:51 Down 192.168.2.80
2014-10-22T23:11:52 #{ResponseTime=7} Up 192.168.2.254
It doesn't matter which one, my preference is the second one
Could you please help me with this matter. Even if it is not possible tell me aswell.
Thank you,
Chris
I'll chime in late, not because the other answer are wrong by any means, they are both functional, but more so because nobody has pointed out that you are recreating the wheel.
You test the connection, and specify an erroraction for it that silently continues leaving your variable null. Then you have to test to see if the variable has results, and treat it one way, or if it doesn't treat it another way. What you have just done is made your own Try/Catch scenario. If you actually use the error to stop you can use the built in Try/Catch. Consider this approach:
$servers = "www.google.com","localhost","www.amazon.com"
$collection = #()
foreach ($server in $servers)
{
Try{
$testconnection = Test-Connection $server -Count 2 -ErrorAction Stop
$testconnection | ForEach{$collection += New-Object PSObject -Property ([ordered]#{
'TimeStamp' = Get-Date -Format s
'Server' = $server
'ResponseTime' = $_.responsetime
'Results' = 'Up'})
}
}
Catch{
$collection += New-Object PSObject -Property ([ordered]#{
'TimeStamp' = Get-Date -Format s
'Server' = $server
'ResponseTime' = $null
'Results' = 'Unreachable'
})
}
}
$collection #| Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
That tries to ping the server, and if it can it adds a custom object to the $collection array with the desired information. If the ping fails it also adds an object to the $collection showing that the server was unreachable.
Also, you had $collection = $(). I assume you were trying to create an empty array, which is correctly done $collection = #() (corrected in my suggested code). Now, I commented out the Export-CSV so I could see the results. This is what I saw:
TimeStamp Server ResponseTime Results
--------- ------ ------------ -------
2014-10-22T17:54:22 www.google.com 9 Up
2014-10-22T17:54:22 www.google.com 12 Up
2014-10-22T17:54:23 localhost 0 Up
2014-10-22T17:54:23 localhost 0 Up
2014-10-22T17:54:27 www.amazon.com Unreachable
Amazon didn't let me ping it, so it shows as unreachable.
Moving on to why your desired results are not practical... What you describe shows you pinging your servers and getting results from them at non-consecutive times. To do that you would have to do -count 1, and loop through the ForEach loop twice, so it would ping server 1 for 1 result, then server 2 for 1 result, then server 3 for 1 result. Then it would go back and ping server 1 for a second result, then server 2 for a second result, and then server 3 for a second result. If you wanted to do that you could I suppose, and it should give you your desired results, you would have to do something like this:
$servers = "www.google.com","localhost","www.amazon.com"
$collection = #()
$count = 2
for($i=1;$i -le $count;$i++){
ForEach($server in $servers){
do stuff to ping servers as described above, except change -count to 1
}
}
$collection | export-CSV '.\ServerStatus.csv' -notype
That will give you your desired results, but it is slower. If you have to run this against more than a few servers it will be noticeably slower. For just those three servers listed it made the entire process go from taking 3.7240945 seconds to taking 7.6104075 seconds (roughly double).
Instead of
$response = ($testconnection | select ResponseTime)
if ($response)
{
$status["Results"] = "Up"
$status["Responsetime"] = $response
}
do
if($testconnection)
{
$testconnection | % {
$status = #{"ServerName" = $server; "TimeStamp" = (Get-Date -f s); "Results" = "Up"; "Responsetime"= $_.responsetime};
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus;
$collection += $serverStatus }
}
else
{
$status = #{"ServerName" = $server; "TimeStamp" = (Get-Date -f s); "Results" = "Down"};
New-Object -TypeName PSObject -Property $status -OutVariable serverStatus;
$collection += $serverStatus
}
The problem is that $testconnection or in your case $response is an array if the count of Test-Connection is greater then 1, so you have to loop through it and add the single entries to your collection.
Also to get the Value instead of the gibberish you get you have to call the .responsetime property.
In hopes I didn't make it too complicated I present this solution
$servers = "10.50.10.100","8.8.8.8","169.254.54.1"
$servers | ForEach-Object{
$server = $_
$timeStamp = (Get-Date -f s)
$testconnection = Test-Connection $server -Count 2 -ErrorAction 0
If(!$testconnection){
$props = #{
Server = $server
TimeStamp = $timeStamp
ResponseTime = ""
Results = "Down"
}
New-Object -TypeName PSObject -Property $props
} Else {
$testconnection | ForEach-Object{
$_ | Select-Object #{l='Server';e={$server}},#{l='TimeStamp';e={$timeStamp}},#{l='ResponseTime';e={$_.ResponseTime}},#{l='Results';e={"Up"}}
}
}
} | Export-Csv -Path ".\ServerStatus.csv" -NoTypeInformation
So your logic is still here but as you can see some things have been changed. Paul was right, in that you needed to loop for each ResponseTime element you had. I also have done that but with a different approach that, if nothing else, will show you some of the Power in PowerShell. A break down of the code
Pipe $servers into a ForEach-Object. ForEach in works fine however I wanted to skip the saving the variables and just output straight to Export-CSV which is why I changed it.
So if you use Test-Connection on a server that does not exist or errors for some reason then you need to create an object to represent that. Using the desired properties, build a object with required values. This is output to the pipe instead of using a temporary variable.
When a connection test is successful then we need to output a number or variables to match the number of returns.
Continuing from #3 we use Select-Object to output the desired values. l stand for label and e for expression. Yes you could easily just use another $props variable. Just illustrating another option.
Since we changed the ForEach in the first step we can just output straight to Export-CSV
Sample output
Server TimeStamp ResponseTime Results
------ --------- ------------ -------
10.50.10.100 2014-10-22T20:22:01 0 Up
10.50.10.100 2014-10-22T20:22:01 0 Up
8.8.8.8 2014-10-22T20:22:02 43 Up
8.8.8.8 2014-10-22T20:22:02 39 Up
169.254.54.1 2014-10-22T20:22:03 Down