Powershell update value in Array - powershell

I'm suspect I am going about this the wrong way and need to switch to a hash table but is it possible to update a specific value in an Array?
The script retrieves the cluster nodes and stores the data in a array, then proceeds to reboot each one, once rebooted I want to update the reboot 'column' value in the array to 1.
$Array=#()
$Cluster = Get-ClusterNode
foreach ($Node in $Cluster)
{
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Node' -Value $Node.Name
$item | Add-Member -type NoteProperty -Name 'State' -Value $Node.State
$item | Add-Member -type NoteProperty -Name 'Rebooted' -Value "0"
$Array += $item
}
foreach ($row in $Array | Where { $Array.Rebooted -eq "0" })
{
Reboot-Function -Node $row.Node
$Array.Rebooted += 1 | Where-Object {$Array.Node -eq $row.Node}
}
$Array

You need to rewrite the Where-Object statement in the second loop slightly (use $_ to refer to the current pipeline object), and then simply update the current object via the $row variable inside the loop body:
foreach ($row in $Array | Where { $_.Rebooted -eq "0" })
{
Reboot-Function -Node $row.Node
$row.Rebooted = '1'
}

Related

Array in a foreach loop

What am I doing wrong here?
The mailbox has an active an inactive mailbox so it will return two mailboxes.
However, when trying to capture the output, I am only getting the last account in the array
Note, this is a simplified version of a larger script, but kept it simple for this example.
$guid = import-csv "c:\temp\Mailboxes.csv"
$DAta = New-Object psobject
$Data | Add-Member -MemberType NoteProperty -Name alias -Value $null
$Data | Add-Member -MemberType NoteProperty -Name guid -Value $null
$mbxcol = #()
#$data = $null
foreach ($G in $Guid){
$mbx = Get-mailbox $g.alias -IncludeInactiveMailbox
$data.alias = $mbx.alias
$data.guid = $mbx.guid
$MBXCol += $Data
}
$mbxcol
As explained in comments, every array element is a reference of the same object ($Data), a simple way to demonstrate using Object.ReferenceEquals Mehod with this example:
foreach ($item in 0..10) {
$data.Alias = 'mailbox{0}' -f $item
$data.Guid = [guid]::NewGuid()
$mbxcol += $data
}
[object]::ReferenceEquals($data, $mbxcol[0]) # -> True
As for how to simplify and make your code more efficient, do not add elements (+=) to a fixed collection (#( )):
$result = (Import-Csv "c:\temp\Mailboxes.csv").Alias |
Get-Mailbox -IncludeInactiveMailbox |
Select-Object Alias, Guid
A much more simple example of your code is:
$guid = ("A","B")
$Data = New-Object psobject
$Data | Add-Member -MemberType NoteProperty -Name alias -Value $null
$mbxcol = #()
foreach ($G in $Guid){
$mbx = $g
$data.alias = $mbx
$MBXCol += $Data
}
$mbxcol
As #Santiago mentioned in his comment, $Data is a reference to an object, so each time you update it, you overwrite it, even if it is in an array. To fix this, instantiate the object each loop as follows:
$guid = ("A","B")
$mbxcol = #()
foreach ($G in $Guid){
$Data = New-Object psobject
$Data | Add-Member -MemberType NoteProperty -Name alias -Value $null
$mbx = $g
$data.alias = $mbx
$MBXCol += $Data
}
$mbxcol

Export list/array to CSV in Powershell

done some googling but answers I have found seem to be more complex than what I need. I have a simple script to fetch DNS cache entries, and I'd like to export the results to a CSV in the most "powershell" manner. Code looks like this:
function Get-Dns
{
$domains = #()
$cmdOutput = Invoke-Command -ScriptBlock {ipconfig /displaydns}
ForEach ($line in $($cmdOutput -split "`r`n"))
{
if ($line -like '*Record Name*'){
$domain = $line -split ":"
$domains += $domain[1]
}
}
so I have an array, $domains, which I would like to use Export-CSV to essentially output a one column CSV with one domain per line. Using Export-CSV seems to just output the length of each element rather than the contents itself. Any help is appreciated!
The most PowerShell way:
(ipconfig /displaydns|where{$_-match'Record Name'})|%{$_.split(":")[1].Trim()}>dnscache.txt
"ipconfig /displaydns" is going to give you back a big'ol string array, which is going to be harder to work with. Try the native commandlets for DNS manipulation:
Get-DnsClientCache | Export-Csv -Path .\stuff.csv
If you're using Windows 7 or earlier, try this...
$dns_client_cache = #()
$raw_dns_data = ipconfig /displaydns
for ($element = 3; $element -le $raw_dns_data.length - 3; $element++) {
if ( $raw_dns_data[$element].IndexOf('Record Name') -gt 0 ) {
if ( $dns_entry ) { $dns_client_cache += $dns_entry }
$dns_entry = New-Object -TypeName PSObject
Add-Member -InputObject $dns_entry -MemberType NoteProperty -Name 'RecordName' -Value $raw_dns_data[$element].Split(':')[1].Trim()
} elseif ( $raw_dns_data[$element].IndexOf('Record Type') -gt 0 ) {
Add-Member -InputObject $dns_entry -MemberType NoteProperty -Name 'RecordType' -Value $raw_dns_data[$element].Split(':')[1].Trim()
} elseif ( $raw_dns_data[$element].IndexOf('Time To Live') -gt 0 ) {
Add-Member -InputObject $dns_entry -MemberType NoteProperty -Name 'TimeToLive' -Value $raw_dns_data[$element].Split(':')[1].Trim()
} elseif ( $raw_dns_data[$element].IndexOf('Data Length') -gt 0 ) {
Add-Member -InputObject $dns_entry -MemberType NoteProperty -Name 'DataLength' -Value $raw_dns_data[$element].Split(':')[1].Trim()
} elseif ( $raw_dns_data[$element].IndexOf('Section') -gt 0 ) {
Add-Member -InputObject $dns_entry -MemberType NoteProperty -Name 'Section' -Value $raw_dns_data[$element].Split(':')[1].Trim()
} elseif ( $raw_dns_data[$element].IndexOf('CNAME Record') -gt 0 ) {
Add-Member -InputObject $dns_entry -MemberType NoteProperty -Name 'CNAMERecord' -Value $raw_dns_data[$element].Split(':')[1].Trim()
}
}
$dns_client_cache | Export-Csv -Path .\dns_stuff.csv -Force -NoTypeInformation
Sorry! I know it's messy.
I ended up going with to export multiple value array to csv
$Data | %{$_} | export-csv -nti -Path C:\

Add-Member inside an outside foreach loop not exporting iterations to CSV

Input:
Servers: [1,2,3]
Services: [a,b,c]
The Server 1 has Service "a"
The Server 2 has Service "a", "b"
The Server 3 has Service "b", "c"
Expected output in CSV (gave correct output on console):
Server_Name ServerAvailability ServiceName[0] ServiceStatus[0] ServiceName[1] ServiceStatus[1]
1 Up a Running
2 Up a Running b Running
3 Up b Running c Running
Actual output in CSV:
Server_Name ServerAvailability ServiceName[0] ServiceStatus[0] ServiceName[1] ServiceStatus[1]
1 Up a Running
2 Up a Running
3 Up b Running
foreach ($s in $servers) {
foreach ($srv in $services) {
$Asrv = Get-Service -Name $srv -ComputerName $s
if ($Asrv -ne $null) {
$HashSrvs.Add($Asrv.Name, $Asrv.Status)
}
}
$infoObject = #()
$infoObject = New-Object PSObject
$infoObject | Add-Member -MemberType NoteProperty -Name "Server name" -Value $s
$infoObject | Add-Member -MemberType NoteProperty -Name "ServerAvailability" -Value $ConStatus
$i=0
foreach ($key in $HashSrvs.GetEnumerator()) {
$infoObject | Add-Member -MemberType NoteProperty -Name "ServiceName[$i]" -Value $key.Key -Force
$infoObject | Add-Member -MemberType NoteProperty -Name "ServiceStatus[$i]" -Value $key.Value -Force
$i++
}
$infoColl += $infoObject
}
$infoColl | Export-Csv -NoTypeInformation -Path .\Server_Inventory_$((Get-Date).ToString('MM-dd-yyyy')).csv -Encoding UTF8
The output is correct. No error shown. The error occurs at the CSV file. The highlighted foreach loop is not iterated. It stops at Service Name[0] and Service Status[0]. Please help me with this.
You're misunderstanding how Export-Csv works. The cmdlet takes a list of objects as input and determines the columns for the CSV from the first object in that list. For all subsequent objects missing properties are filled with null values and additional properties are omitted.
To get the result you want you'd need to know the maximum number of services and add that number of properties to each object. Alternatively you could create the CSV manually by building a list of comma-separated strings and writing that via Set-Content or Out-File. Both is doable, of course, but a much simpler, much more straightforward approach would be putting the list of services into a single column with a different delimiter:
$infoColl = foreach ($s in $servers) {
$svc = Get-Service -Name $services -Computer $s -ErrorAction SilentlyContinue |
ForEach-Object { '{0}={1}' -f ($_.Name, $_.Status) }
New-Object PSObject -Property #{
'Server name' = $s
'ServerAvailability' = $ConStatus
'Services' = $svc -join '|'
}
}

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

Want to Wrap PSList in a Powershell function to use pipeline values

I like PSList, and use the CPU and Elapsed times to indentify processes that need to be killed. I would like to wrap it in a powershell function that returns pipeline values so that I could do something like the following:
get-remoteprocess server1 | where {$_.CPUMinutes -gt 4}
I get stuck in returning values from the function - which I understand would need to be an array of objects.
Heres what my first version looks like:
function get-remoteproc {
param ($hostname)
$results = pslist "\\$hostname"
for ($i= 3; $i -le $results.length; $i++) {
$strline = $results[$i]
$StrWithPrefix = " "+$results[$i]
$trimmedline = $StrWithPrefix -replace '\s+', " ";
$Splitline = $trimmedline.Split(" ")
$ProcessName = $Splitline[1]
.
.
$ProcessCPUTime = ":"+[string]$Splitline[7]
$SplitCpuTime = $ProcessCPUTime.Split(":")
$CpuHours = $SplitCpuTime[1]
$CpuMinutes = $SplitCpuTime[2]
$CpuSeconds = $SplitCpuTime[3]
.
.
.
$obj = New-Object PSObject
$obj | Add-Member Noteproperty -Name "Name" -Value $Name
$obj | Add-Member Noteproperty -Name "PPid" -Value $Ppid
$obj | Add-Member Noteproperty -Name "Pri" -Value $Pri
}
Taking Doug Finke's suggestion, which is pleasingly terse, here is my slightly adapted version, which works well with pipeline values.
function get-remoteproc {
param ($hostname=$env:COMPUTERNAME)
$results = PsList "\\$hostname"
foreach($record in $results[3..($results.Count)]) {
# Remove spaces
while ($record.IndexOf(" ") -ge 0) {
$record = $record -replace " "," "
}
$Name,$Processid,$Pri,$Thd,$Hnd,$Priv,$CPUTime,$ElapsedTime = $record.Split(" ")
$properties = #{
Name = $Name
Pid = $Processid
Pri = $Pri
Thd = $Thd
Hnd = $Hnd
Priv = $Priv
CPUTime = $CPUTime
ElapsedTime = $ElapsedTime
}
New-Object PSObject -Property $properties |
Add-Member -PassThru ScriptProperty CPUH {[int]$this.CPUTime.Split(":")[0]} |
Add-Member -PassThru ScriptProperty CPUM {[int]$this.CPUTime.Split(":")[1]} |
Add-Member -PassThru ScriptProperty CPUS {[int]$this.CPUTime.Split(":")[2]} |
Add-Member -PassThru ScriptProperty ElaH {[int]$this.ElapsedTime.Split(":")[0]} |
Add-Member -PassThru ScriptProperty ElaM {[int]$this.ElapsedTime.Split(":")[1]} |
Add-Member -PassThru ScriptProperty ElaS {[int]$this.ElapsedTime.Split(":")[2]}
}
}
And a call to the function, which shows that the objects are unrolled correctly for consumption by the pipeline:
get-remoteproc "Server1" | where {(($_.CPUM * $_.CPUS) -gt 60) -and ($_.name -notmatch "Idle" )}|
ft name, pid, pri, thd, hnd, priv, CPUH, cpuM, cpuS, ElaH, ElaM, ElaS -auto
Thanks, everyone!
As empo points out, you need to emit the $obj back to the pipeline. Here is another way to work the plist text into PowerShell objects.
function get-remoteproc {
param ($hostname=$env:COMPUTERNAME)
$results = PsList "\\$hostname"
foreach($record in $results[3..($results.Count)]) {
# Remove spaces
while ($record.IndexOf(" ") -ge 0) {
$record = $record -replace " "," "
}
$Name,$Processid,$Pri,$Thd,$Hnd,$Priv,$CPUTime,$ElapsedTime = $record.Split(" ")
$properties = #{
Name = $Name
Pid = $Processid
Pri = $Pri
Thd = $Thd
Hnd = $Hnd
Priv = $Priv
CPUTime = $CPUTime
ElapsedTime = $ElapsedTime
}
New-Object PSObject -Property $properties |
Add-Member -PassThru ScriptProperty CPUHours {$this.CPUTime.Split(":")[0]} |
Add-Member -PassThru ScriptProperty CPUMinutes {$this.CPUTime.Split(":")[1]} |
Add-Member -PassThru ScriptProperty CPUSeconds {$this.CPUTime.Split(":")[2]}
}
}
To return an array of values passed to the pipeline one by one, you just need to return multiple values in your function. For example:
function get-remoteproc {
param ($hostname)
$results = pslist "\\$hostname"
for ($i= 3; $i -le $results.length; $i++) {
# your staff to $obj
# output $obj
$obj
}
}
The function result is an array of $obj. When connected to a pipeline the array will be enrolled, and all the values will be passed to the stream one by one.