Merge Variables to one Object - powershell

I have multiple variables, containing Information about my Servers. e.g
PS Z:\Powershell-Scripts> $AllRam
Computername RAM
------------ ---
ServerA 14.00
ServerB 80.00
ServerC 64.00
ServerD 48.00
ServerE 72.00
PS Z:\Powershell-Scripts> $AllProcessor
ComputerName ProcessorCount LogicalProcessors
------------ -------------- -----------------
ServerA 2 4
ServerB 2 32
ServerC 2 24
ServerD 1 12
ServerE 2 24
I have about 10 Variables containing different information.
Now I would like to merge them, so that I have one big Variable with all the information. So the example above should look like this in the end:
ComputerName ProcessorCount LogicalProcessors RAM
------------ -------------- ----------------- ---
ServerA 2 4 14.00
ServerB 2 32 80.00
ServerC 2 24 64.00
ServerD 1 12 48.00
ServerE 2 24 72.00
How could I achieve that? The ComputerName column exists in all Variables and all Servers exist in all Variables.

Here's one way to do it.
Foreach ($item in $AllProcessor) {
$RamItem = $AllRam.Where({$_.ComputerName -eq $item.ComputerName},'first')
Add-Member -MemberType NoteProperty -Name 'RAM' -Value $RamItem.Ram -InputObject $item
}
Here's another if you needed to create a new object without modifying existing one.
$ALLItems = Foreach ($item in $AllProcessor) {
$RamItem = $AllRam.Where( { $_.ComputerName -eq $item.ComputerName }, 'first')
[PSCustomObject]#{
ComputerName = $item.ComputerName
ProcessorCount = $item.ProcessorCount
LogicalProcessor = $item.LogicalProcessor
Ram = $RamItem.Ram
}
}
Result
Data used for reference
$AllRam = #(
[PSCustomObject]#{ComputerName = 'ServerA'; RAM = '14.00' }
[PSCustomObject]#{ComputerName = 'ServerB'; RAM = '80.00' }
[PSCustomObject]#{ComputerName = 'ServerC'; RAM = '64.00' }
[PSCustomObject]#{ComputerName = 'ServerD'; RAM = '48.00' }
[PSCustomObject]#{ComputerName = 'ServerE'; RAM = '72.00' }
)
$AllProcessor = #(
[PSCustomObject]#{ComputerName = 'ServerA'; ProcessorCount = 2; LogicalProcessor = 4 }
[PSCustomObject]#{ComputerName = 'ServerB'; ProcessorCount = 2; LogicalProcessor = 32 }
[PSCustomObject]#{ComputerName = 'ServerC'; ProcessorCount = 2; LogicalProcessor = 24 }
[PSCustomObject]#{ComputerName = 'ServerD'; ProcessorCount = 1; LogicalProcessor = 12 }
[PSCustomObject]#{ComputerName = 'ServerE'; ProcessorCount = 2; LogicalProcessor = 24 }
)

Related

How to add values from nested hashTable values

Below is my code. I would like to add then read individual values.
$ht = #{
'Hcohesity01' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
'Hcohesity02' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
}
$ht['Hcohesity02']['percentage']['server4'] = 20
foreach ( $value -in $ht['Hcohesity02']['percentage'].Values){
$server5 += $value
}
$ht['Hcohesity02']['percentage']['server5'] =$server5
$ht['Hcohesity02']['percentage']
Below code is not working , any idea ?
foreach ( $value -in $ht['Hcohesity02']['percentage'].Values)
The only real mistake in your code is by writing -in in the foreach loop. That should have been just in.
Instead of using a loop to add-up the values, you can use a one-liner with Measure-Object:
$ht = #{
'Hcohesity01' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
'Hcohesity02' = #{
'Audit' = 1
'Block' = 2
'Change' = 3
'percentage' = #{
'server1' = 4
'server2' = 5
'server3' = 10
}
}
}
$ht['Hcohesity02']['percentage']['server4'] = 20
# instead of a foreach loop:
# $server5 = 0 # initialize
# foreach ( $value in $ht['Hcohesity02']['percentage'].Values) {
# $server5 += $value
# }
# you can do this:
$server5 = ($ht['Hcohesity02']['percentage'].Values | Measure-Object -Sum).Sum
$ht['Hcohesity02']['percentage']['server5'] = $server5
$ht['Hcohesity02']['percentage']
Result:
Name Value
---- -----
server2 5
server5 39
server3 10
server1 4
server4 20
If you don't like addressing the properties with the [property] syntax, you can also use dot notation as hoppy7 showed in his answer:
$ht.'Hcohesity02'.'percentage'.Values # and so on
Its not quite clear what you're trying to do. If its just iterating through your foreach loop and assigning a value, you need to ditch the dash on "-in". It should look like the below:
foreach ($value in $ht.Hcohesity02.percentage.Values)
{
# do some stuff
}

Remote PowerShell, find last 5 user logins

I am attempting to view the last 5 login events on an Enterprise machine as an Admin after a security event. I do initial investigations and am trying to find a way to quickly spit out a list of potential, 'suspects'.
I have been able to generate output that lists the logfile but under account name where you would generally see \Domain\username I only get the output, "SYSTEM" or similar.
If I had recently remoted into the machine it will pull my \Domain\Username and display it no problem.
Ideally I would like to make a script that pulls the logon events from a machine on the network with a list of who logged in recently and when.
This is what I have so far:
Get-EventLog -LogName security -InstanceId 4624 -ComputerName $_Computer -Newest 5 | Export-Csv C:\Users\username\Documents\filename
this uses the far faster Get-WinEvent cmdlet & the -FilterHashtable parameter to both speed things up a tad and to add more selectors. you may want to remove some of the filters - this was written quite some time ago for another project. [grin]
#requires -RunAsAdministrator
# there REALLY otta be a way to get this list programmatically
$LogonTypeTable = [ordered]#{
'0' = 'System'
'2' = 'Interactive'
'3' = 'Network'
'4' = 'Batch'
'5' = 'Service'
'6' = 'Proxy'
'7' = 'Unlock'
'8' = 'NetworkCleartext'
'9' = 'NewCredentials'
'10' = 'RemoteInteractive'
'11' = 'CachedInteractive'
'12' = 'CachedRemoteInteractive'
'13' = 'CachedUnlock'
}
$EventLevelTable = [ordered]#{
LogAlways = 0
Critical = 1
Error = 2
Warning = 3
Informational = 4
Verbose = 5
}
$WantedLogonTypes = #(2, 3, 10, 11)
$AgeInDays = 15
$StartDate = (Get-Date).AddDays(-$AgeInDays)
$ComputerName = $env:COMPUTERNAME
$GWE_FilterHashTable = #{
Logname = 'Security'
ID = 4624
StartTime = $StartDate
#Level = 2
}
$GWE_Params = #{
FilterHashtable = $GWE_FilterHashTable
ComputerName = $ComputerName
MaxEvents = 100
}
$RawLogonEventList = Get-WinEvent #GWE_Params
$LogonEventList = foreach ($RLEL_Item in $RawLogonEventList)
{
$LogonTypeID = $RLEL_Item.Properties[8].Value
if ($LogonTypeID -in $WantedLogonTypes)
{
[PSCustomObject]#{
LogName = $RLEL_Item.LogName
TimeCreated = $RLEL_Item.TimeCreated
UserName = $RLEL_Item.Properties[5].Value
LogonTypeID = $LogonTypeID
LogonTypeName = $LogonTypeTable[$LogonTypeID.ToString()]
}
}
}
$NewestLogonPerUser = $LogonEventList |
Sort-Object -Property UserName |
Group-Object -Property UserName |
ForEach-Object {
if ($_.Count -gt 1)
{
$_.Group[0]
}
else
{
$_.Group
}
}
$NewestLogonPerUser
current output on my system ...
LogName : Security
TimeCreated : 2019-01-24 1:50:44 PM
UserName : ANONYMOUS LOGON
LogonTypeID : 3
LogonTypeName : Network
LogName : Security
TimeCreated : 2019-01-24 1:50:50 PM
UserName : [MyUserName]
LogonTypeID : 2
LogonTypeName : Interactive
I've been fiddling with this too and also decided to use the Get-WinEvent cmdlet for this, because unfortunately, using Get-EventLog the info you want is all in the .Message item and that is a localized string..
My approach is a little different than Lee_Daily's answer as I get the info from the underlying XML like this:
#logon types: https://learn.microsoft.com/en-us/windows/desktop/api/ntsecapi/ne-ntsecapi-_security_logon_type#constants
$logonTypes = 'System','Undefined','Interactive','Network','Batch','Service','Proxy','Unlock',
'NetworkCleartext','NewCredentials','RemoteInteractive','CachedInteractive',
'CachedRemoteInteractive','CachedUnlock'
$dataItems = #{
SubjectUserSid = 0
SubjectUserName = 1
SubjectDomainName = 2
SubjectLogonId = 3
TargetUserSid = 4
TargetUserName = 5
TargetDomainName = 6
TargetLogonId = 7
LogonType = 8
LogonProcessName = 9
AuthenticationPackageName = 10
WorkstationName = 11
LogonGuid = 12
TransmittedServices = 13
LmPackageName = 14
KeyLength = 15
ProcessId = 16
ProcessName = 17
IpAddress = 18
IpPort = 19
}
$result = Get-WinEvent -FilterHashtable #{LogName="Security";Id=4624} -MaxEvents 100 | ForEach-Object {
# convert the event to XML and grab the Event node
$eventXml = ([xml]$_.ToXml()).Event
# get the 'TargetDomainName' value and check it does not start with 'NT AUTHORITY'
$domain = $eventXml.EventData.Data[$dataItems['TargetDomainName']].'#text'
if ($domain -ne 'NT AUTHORITY' ) {
[PSCustomObject]#{
Domain = $domain
UserName = $eventXml.EventData.Data[$dataItems['TargetUserName']].'#text'
UserSID = $eventXml.EventData.Data[$dataItems['TargetUserSid']].'#text'
LogonType = $logonTypes[[int]$eventXml.EventData.Data[$dataItems['LogonType']].'#text']
Date = [DateTime]$eventXml.System.TimeCreated.SystemTime
Computer = $eventXml.System.Computer
}
}
}
$result | Sort-Object Date -Descending | Group-Object -Property UserName | ForEach-Object {
if ($_.Count -gt 1) { $_.Group[0] } else { $_.Group }
} | Format-Table -AutoSize
On my machine the output looks like
Domain UserName UserSID LogonType Date Computer
------ -------- ------- --------- ---- --------
MyDomain MyUserName S-1-5-21-487608883-1237982911-748711624-1000 Interactive 27-1-2019 20:36:45 MyComputer
MyDomain SomeoneElse S-1-5-21-487608883-1237982911-748765431-1013 Interactive 27-1-2019 18:36:45 MyComputer

Get random items from hashtable but the total of values has to be equal to a set number

I'm trying to build a simple "task distributor" for the house tasks between me and my wife. Although the concept will be really useful at work too so I need to learn it properly.
My hashtable:
$Taches = #{
"Balayeuse plancher" = 20
"Moppe plancher" = 20
"Douche" = 15
"Litières" = 5
"Poele" = 5
"Comptoir" = 5
"Lave-Vaisselle" = 10
"Toilette" = 5
"Lavabos" = 10
"Couvertures lit" = 5
"Poubelles" = 5
}
The total value for all the items is 105 (minutes).
So roughly 50mins each of we split it in two.
My goal:
I want to select random items from that hashtable and build two different hashtables - one for me and my wife, each having a total value of 50 (So it's fair). For example 20+20+10 or 5+5+5+15+20, etc. The hard part is that ALL tasks have to be accounted for between the two hashtables and they can only be present ONCE in each of them (no use in cleaning the same thing twice!).
What would be the best option?
For now I successfully achieved a random hashtable of a total value of 50 like this:
do {
$Me = $null
$sum = $null
$Me = #{}
$Me = $Taches.GetEnumerator() | Get-Random -Count 5
$Me | ForEach-Object { $Sum += $_.value }
} until ($sum -eq 50)
Result example :
Name Value
---- -----
Poubelles 5
Balayeuse plancher 20
Douche 15
Poele 5
Toilette 5
It works but boy does it feel like it's a roundabout and crooked way of doing it. I'm sure there is a better approach? Plus I'm lacking important things. ALL the tasks have to be accounted for and not be present twice. This is quite complicated although it looked simple at first!
You can not maximise randomness and fairness at the same time so one has to give. I think you should not risk being unfair to your wife and so fairness must prevail!
Fairness at the expense of randomness
This approach sorts the items in descending time order and then randomly assigns them items to each person unless that assignment would be unfair.
The fairness calculation here is that the maximum time difference should be at most the duration of the quickest task.
$DescendingOrder = $Taches.Keys | Sort-Object -Descending { $Taches[$_] }
$Measures = $Taches.Values | Measure-Object -Sum -Minimum
$UnfairLimit = ($Measures.Sum + $Measures.Minimum) / 2
$Person1 = #{}
$Person2 = #{}
$Total1 = 0
$Total2 = 0
foreach ($Item in $DescendingOrder) {
$Time = $Taches[$Item]
$Choice = Get-Random 2
if (($Choice -eq 0) -and (($Total1 + $Time) -gt $UnfairLimit)) {
$Choice = 1
}
if (($Choice -eq 1) -and (($Total2 + $Time) -gt $UnfairLimit)) {
$Choice = 0
}
if ($Choice -eq 0) {
$Person1[$Item] = $Time
$Total1 += $Time
} else {
$Person2[$Item] = $Time
$Total2 += $Time
}
}
An example run:
PS> $Person1 | ConvertTo-Json
{
"Comptoir": 5,
"Lavabos": 10,
"Litières": 5,
"Couvertures lit": 5,
"Douche": 15,
"Lave-Vaisselle": 10
}
and the other person:
PS> $Person2 | ConvertTo-Json
{
"Moppe plancher": 20,
"Toilette": 5,
"Balayeuse plancher": 20,
"Poubelles": 5,
"Poele": 5
}
Randomness at the expense of fairness
This approach is to randomize the list, go through each item and then assign it to the person who has the least time allocated to them so far.
Earlier decisions might mean that later decisions end up being unfair.
$RandomOrder = $Taches.Keys | Sort-Object { Get-Random }
$Person1 = #{}
$Person2 = #{}
$Total1 = 0
$Total2 = 0
foreach ($Item in $RandomOrder) {
$Time = $Taches[$Item]
if ($Total1 -lt $Total2) {
$Person1[$Item] = $Time
$Total1 += $Time
} else {
$Person2[$Item] = $Time
$Total2 += $Time
}
}
An example run:
PS> $Person1 | ConvertTo-Json
{
"Poele": 5,
"Douche": 15,
"Couvertures lit": 5,
"Lave-Vaisselle": 10,
"Balayeuse plancher": 20,
"Toilette": 5
}
and the other person:
PS> $Person2 | ConvertTo-Json
{
"Lavabos": 10,
"Comptoir": 5,
"Poubelles": 5,
"Litières": 5,
"Moppe plancher": 20
}
You should probably write the algorithm to always have you take the extra task in a rounding error (Happy Wife, Happy Life).
This is probably over-engineered, but I was intrigued by the question, and learned some French in the process.
$Taches = #{
"Balayeuse plancher" = 20
"Moppe plancher" = 20
"Douche" = 15
"Litières" = 5
"Poele" = 5
"Comptoir" = 5
"Lave-Vaisselle" = 10
"Toilette" = 5
"Lavabos" = 10
"Couvertures lit" = 5
"Poubelles" = 5
}
$target = 0
$epsilon = 5
# copy if you don't want to destroy original list (not needed probably)
# put all entries in first list.
# randomly move entry to p2 if count over target +/- epsilon
# randomly move entry from p2 if count under target +/- epsilon
# (unless you know you can always get exactly target and not loop forever trying)
$p1 = #{} # person 1
$p2 = #{} # person 2
$p1Total = 0 # optimizaton to not have to walk entire list and recalculate constantly
$p2Total = 0 # might as well track this too...
$Taches.Keys | % {
$p1.Add($_, $Taches[$_])
$p1Total += $Taches[$_]
$target += $Taches[$_]
}
$target = $target / 2
$done = $false
while (-not $done)
{
if ($p1Total -gt ($target+$epsilon))
{
$item = $p1.Keys | Get-Random
$value = $p1[$item]
$p1.Remove($item)
$p2.Add($item, $value)
$p1Total -= $value
$p2Total += $value
continue
}
elseif ($p1Total -lt ($target-$epsilon))
{
$item = $p2.Keys | Get-Random
$value = $p2[$item]
$p2.Remove($item)
$p1.Add($item, $value)
$p1Total += $value
$p2Total -= $value
continue
}
$done = $true
}
"Final result"
"p1"
$p1Total
$p1
"`np2"
$p2Total
$p2
Yet another approach:
$MinSum = ($Taches.Values | Measure-Object -Minimum ).Minimum
$HalfSum = ($Taches.Values | Measure-Object -Sum ).Sum / 2
do {
$sum = 0
$All = $Taches.GetEnumerator() |
Get-Random -Count $Taches.Keys.Count
$Me = $All | ForEach-Object {
if ( $Sum -lt $HalfSum - $MinSum ) {
$Sum += $_.value
#{ $_.Key = $_.Value }
}
}
Write-Host "$sum " -NoNewline # debugging output
} until ($sum -eq 50 )
$Em = $Taches.Keys | ForEach-Object {
if ( $_ -notin $Me.Keys ) {
#{ $_ = $Taches.$_ }
}
}
# show "fairness" (task count vs. task cost)
$Me.Values | Measure-Object -Sum | Select-Object -Property Count, Sum
$Em.Values | Measure-Object -Sum | Select-Object -Property Count, Sum
Sample output(s):
PS D:\PShell> D:\PShell\SO\54610011.ps1
50
Count Sum
----- ---
4 50
7 55
PS D:\PShell> D:\PShell\SO\54610011.ps1
65 65 50
Count Sum
----- ---
6 50
5 55
Great answers guys, learned a lot. Here is what I ended up doing thanks to "Fischfreund" on Reddit (https://www.reddit.com/r/PowerShell/comments/aovs8s/get_random_items_from_hashtable_but_the_total_of/eg3ytds).
His approach is amazingly simple yet I didn't think of it at all.
First hashtable : Get a random count of 5 until the sum is 50. Then create a second hashtable where the items are not in the first hashtable! I assign that first hahstable containing 5 items to my wife so I'm the one who always has an extra task (like suggested by Kory ;)). Phew i'm safe.
$Taches = #{
"Balayeuse plancher" = 20
"Moppe plancher" = 20
"Douche" = 15
"Litières" = 5
"Poele" = 5
"Comptoir" = 5
"Lave-Vaisselle" = 10
"Toilette" = 5
"Lavabos" = 10
"Couvertures lit" = 5
"Poubelles" = 5
}
do {
$Selection1 = $Taches.GetEnumerator() | Get-Random -Count 5
} until (($Selection1.Value | measure -Sum ).Sum -eq 50)
$Selection2 = $Taches.GetEnumerator() | Where-Object {$_ -notin $Selection1}
$Selection1 | select-object #{Name="Personne";expression={"Wife"} },Name,Value
""
$Selection2 | select-object #{Name="Personne";expression={"Me"} },Name,Value

Display Disk Size and FreeSpace in GB

Is there one line of code that will display free-size and disk-space of your logical disk in gb instead of mb? I tried doing some research but I couldn't find one liner, I did give this an attempt which was to divide it by 1GB, but that didn't work, how can I accomplish this?
gwmi win32_logicaldisk | Format-Table DeviceId, MediaType,Size,FreeSpace /1GB
Try calculated properties. I would also add [math]::Round() to shorten the values:
gwmi win32_logicaldisk | Format-Table DeviceId, MediaType, #{n="Size";e={[math]::Round($_.Size/1GB,2)}},#{n="FreeSpace";e={[math]::Round($_.FreeSpace/1GB,2)}}
n stands for name and e for expression. You could use the full names too, but it's a waste of space if you're writing multiple calculated Properties.
When performing any arithmetical calculation, it should be put in { }.
gwmi win32_logicaldisk | Format-Table DeviceId, MediaType,Size, {$_.FreeSpace /1GB}
You can read more on syntax from Microsoft Powershell Library
I'd like to provide an alternate/updated answer. (As of Powershell 5 at least, probably version 3.)
Just use Get-Volume
https://learn.microsoft.com/en-us/powershell/module/storage/get-volume
Example:
> get-volume
DriveLetter FriendlyName FileSystemType DriveType HealthStatus OperationalStatus SizeRemaining Size
----------- ------------ -------------- --------- ------------ ----------------- ------------- ----
FAT32 Fixed Healthy OK 451 MB 496 MB
C OSDISK NTFS Fixed Healthy OK 65.23 GB 474.3 GB
X Transfer_Shuttle NTFS Fixed Healthy OK 37.65 GB 48.68 GB
I have same issue and I want get KB,MB,GB,TB,PB on Server with Windows Server 2008 to 2016,
then I google many information to build a function:
function ConvertDoubleToBytes([double]$srcDouble){
if([Math]::Floor([Math]::Log($srcDouble,1024)) -eq 0){
$resn = ([Math]::Ceiling($srcDouble/([math]::pow(1024,([Math]::Floor([Math]::Log($srcDouble,1024)))))).ToString() + " Bytes")
} elseif ([Math]::Floor([Math]::Log($srcDouble,1024)) -eq 1){
$resn = ([Math]::Ceiling($srcDouble/([math]::pow(1024,([Math]::Floor([Math]::Log($srcDouble,1024)))))).ToString() + " KB")
} elseif ([Math]::Floor([Math]::Log($srcDouble,1024)) -eq 2){
$resn = ([Math]::Ceiling($srcDouble/([math]::pow(1024,([Math]::Floor([Math]::Log($srcDouble,1024)))))).ToString() + " MB")
} elseif ([Math]::Floor([Math]::Log($srcDouble,1024)) -eq 3){
$resn = ([Math]::Ceiling($srcDouble/([math]::pow(1024,([Math]::Floor([Math]::Log($srcDouble,1024)))))).ToString() + " GB")
} elseif ([Math]::Floor([Math]::Log($srcDouble,1024)) -eq 4){
$resn = ([Math]::Ceiling($srcDouble/([math]::pow(1024,([Math]::Floor([Math]::Log($srcDouble,1024)))))).ToString() + " TB")
} elseif ([Math]::Floor([Math]::Log($srcDouble,1024)) -eq 5){
$resn = ([Math]::Ceiling($srcDouble/([math]::pow(1024,([Math]::Floor([Math]::Log($srcDouble,1024)))))).ToString() + " PB")
}
return $resn
}
And I use it:
Get-WmiObject -query "Select * from win32_logicaldisk " | Select-Object DeviceID,#{n="FreeSpace";e={ConvertDoubleToBytes($([double]::Parse($_.FreeSpace)))}},#{n="Size";e={ConvertDoubleToBytes($([double]::Parse($_.Size)))}},#{n="Useage Percent";e={$([Math]::Ceiling((1-$($([double]::Parse($_.FreeSpace))/$([double]::Parse($_.Size))))*100)).ToString() + " %"}}
Will print like this:
DeviceID FreeSpace Size Useage Percent
-------- --------- ---- --------------
C: 36 GB 100 GB 65 %
D: 294 GB 600 GB 52 %
E:
F: 279 GB 600 GB 54 %
G:
H: 500 GB 500 GB 1 %
This works great.
$disks = get-wmiobject Win32_LogicalDisk -computername $computer -Filter "DriveType = 3"
foreach ($disk in $disks) {
$letter = $disk.deviceID
$volumename = $disk.volumename
$totalspace = [math]::round($disk.size / 1GB, 2)
$freespace = [math]::round($disk.freespace / 1GB, 2)
$usedspace = [math]::round(($disk.size - $disk.freespace) / 1GB, 2)
$disk | Select-Object #{n = "Computer Name"; e = { $computer } }, #{n = "Disk Letter"; e = { $letter } }, #{n = "Volume Name"; e = { $volumename } }, #{n = "Total Space"; e = { ($totalspace).tostring() + " GB" } }, #{n = "Free Space"; e = { ($freespace).tostring() + " GB" } }, #{n = "Used Space"; e = { ($usedspace).tostring() + " GB" } } | FT
}

Working with CSV files

Suppose I have a table like this in data.csv:
time channel x y z
0.001 point 1 1 2 3
0.001 point 2 4 5 6
0.001 point 3 7 8 9
0.001 point 4 10 11 12
0.001 point 5 13 14 15
0.002 point 1 2 3 4
0.002 point 2 5 6 7
0.002 point 3 8 9 10
0.002 point 4 11 12 13
0.002 point 5 14 15 16
0.004 point 1 3 4 5
0.004 point 2 6 7 8
0.004 point 3 9 10 11
0.004 point 4 12 13 14
0.004 point 5 15 16 17
How do I make Powershell to write out 3 files (Xdata.csv, Ydata.csv, Zdata.csv), which Xdata.csv should look like this:
time point 1 point 2 point 3 point 4 point 5
0.001 1 4 7 10 13
0.002 2 5 8 11 14
0.004 3 6 9 12 15
So far, my code looks like this:
# Path to current directory
$path = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)
# csv filename to be imported from
$filename = "data.csv"
$data = Import-Csv "$path\$filename"
# Array of unique values of times
$times = $data | % { $_.time } | Sort-Object | Get-Unique
# Array of unique values of channels
$channels = $data | % { $_.channel } | Sort-Object | Get-Unique
But at this point, I am struggling of how to set up an output table just like the one above.
I would use Group-Object + some logic to generate objects using data from each 'time' snapshot
$collections = #{
x = #()
y = #()
z = #()
}
$data | Group time | ForEach-Object {
$x = New-Object PSObject -Property #{
time = $_.Name
}
$y = New-Object PSObject -Property #{
time = $_.Name
}
$z = New-Object PSObject -Property #{
time = $_.Name
}
foreach ($item in $_.Group) {
if ($item.channel) {
$x | Add-Member -MemberType NoteProperty -Name $item.channel -Value $item.x
$y | Add-Member -MemberType NoteProperty -Name $item.channel -Value $item.y
$z | Add-Member -MemberType NoteProperty -Name $item.channel -Value $item.z
}
}
$collections.x += $x
$collections.y += $y
$collections.z += $z
}
foreach ($coordinate in 'x','y','z') {
$collections.$coordinate | Export-Csv -NoTypeInformation "${coordinate}data.csv"
}
This is with assumption that 'data' contains object similar to one I could generate with:
New-Object PSObject -Property #{
time = 0.001
channel = 'point 1'
x = 1
y = 2
z = 3
}
More of a pipeline approach (not tested)
$axes = #{
x = [ordered]#{}
y = [ordered]#{}
z = [ordered]#{}
}
import-csv data.csv |
foreach-object {
if ( $axes.x.containskey($_.time) )
{
foreach ($axis in 'x','y','z')
{ $axes.$axis[$_.time].add($_.channel,$_.$axis) }
}
else {
foreach ($axis in 'x','y','z')
{ $axes.$axis[$_.time] = [ordered]#{ time = $_.time;$_.channel = $_.$axis } }
}
}
foreach ($axis in 'x','y','z')
{
$(foreach ($time in $axes.$axis.values)
{ [PSCustomObject]$time })|
export-csv ${$axis}data.csv -NoTypeInformation
}