PowerShell ForEach loop returning only One Result - arrays

I feel like perhaps I am overlooking something simple here, but I am having trouble with a ForEach loop in PowerShell actually returning all items that I expect. I have a script that will query an Oracle database and gather up the base data set. Once this is gathered, I will need to perform some adjustments to what is returned and build an additional bit of information (not in the script currently, working through the basics so far)
What I am doing is adding the data to an array, then trying to use a ForEach loop to examine each item in the array and pump that data out to another array that will have the new properties that I need to populate based on some calculations of the base data set. What I am getting returned to the variable $finaloutput is only one line of the data (for the example I am posting here I simply look for one reportnumber equal to CPOD-018, which there are 5 of in the data set with varying other properties populated including the sitename which I am populate as well, but still only get one result).
I've tried going about this using nested if statements within the ForEach loop instead of the piped Where-Object, but received the same results. Below is the current version of the script, any assistance would be greatly appreciated.
param(
[parameter(mandatory=$True)]$username,
[parameter(mandatory=$True)]$password
)
# setup the finaloutput variable
$finaloutput = New-Object psobject
$finaloutput | Add-Member -MemberType NoteProperty -name ReportNumber -value NotSet
$finaloutput | Add-Member -MemberType NoteProperty -name sitename -value NotSet
# the connection string to be used by the OlEDB connection
$connString = #"
Provider=OraOLEDB.Oracle;Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST="host.host.host")(PORT="1521"))
(CONNECT_DATA=(SERVICE_NAME="name.name.name")));User ID="$username";Password="$password"
"#
# the query that will be used to gather data from Oracle
$qry= #"
select VP_EXPECTED_DETAILS.REPORTNUMBER, VP_EXPECTED_DETAILS.SITE_NAME, VP_ACTUAL_FILENAME_DETAILS.FILE_NAME, VP_EXPECTED_DETAILS.MAX_EXPECTED_LOAD_TIME, VP_EXPECTED_DETAILS.EXPECTED_FREQUENCY,
VP_EXPECTED_DETAILS.DATE_TIMING, VP_EXPECTED_DETAILS.FREQUENCY_DAY, VP_EXPECTED_DETAILS.JOB_NO,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.GEN_PARSE_IN,'YYYYMMDDHH24MISS') AS GEN_PARSE_IN,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.GEN_PARSE_OUT,'YYYYMMDDHH24MISS') AS GEN_PARSE_OUT,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.ETLLOADER_IN,'YYYYMMDDHH24MISS') AS ETLLOADER_IN,
TO_CHAR(VP_ACTUAL_RPT_DETAILS.ETLLOADER_OUT,'YYYYMMDDHH24MISS') AS ETLLOADER_OUT
from MONITOR.VP_EXPECTED_DETAILS
LEFT JOIN MONITOR.VP_ACTUAL_RPT_DETAILS on VP_EXPECTED_DETAILS.REPORTNUMBER = VP_ACTUAL_RPT_DETAILS.REPORTNUMBER and VP_EXPECTED_DETAILS.SITE_NAME = VP_ACTUAL_RPT_DETAILS.SITE_NAME
LEFT JOIN MONITOR.VP_ACTUAL_FILENAME_DETAILS on VP_ACTUAL_RPT_DETAILS.FNKEY = VP_ACTUAL_FILENAME_DETAILS.FNKEY where VP_EXPECTED_DETAILS.EXPECTED_FREQUENCY = 'DAILY' or
(VP_EXPECTED_DETAILS.EXPECTED_FREQUENCY = 'MONTHLY' AND VP_EXPECTED_DETAILS.FREQUENCY_DAY = EXTRACT(DAY from SYSDATE))
"#
# the function that will open the database connection and execute the query
function Get-OLEDBData ($connectstring, $sql) {
$OLEDBConn = New-Object System.Data.OleDb.OleDbConnection($connectstring)
$OLEDBConn.open()
$readcmd = New-Object system.Data.OleDb.OleDbCommand($sql,$OLEDBConn)
$readcmd.CommandTimeout = '300'
$da = New-Object system.Data.OleDb.OleDbDataAdapter($readcmd)
$dt = New-Object System.Data.DataTable
[void]$da.fill($dt)
$OLEDBConn.close()
return $dt
}
# populate $output with the data from the Get-OLEDBData function
$output = Get-OLEDBData $connString $qry
# build the final output that will generate alerts
ForEach ($lines in $output | Where-Object {$_.reportnumber -eq "CPOD-018"})
{
$finaloutput.reportnumber = $lines.reportnumber
$finaloutput.sitename = $lines.SITE_NAME
}
$finaloutput

It looks like what is happening is you are doing the object creation incorrectly. Inside the foreach loop you keep overwriting the same values rather than appending new objects
It should look more like this:
$FinalOutput = ForEach ($lines in $output | Where-Object {$_.reportnumber -eq "CPOD-018"})
{
$Prop = #{
'reportnumber' = $lines.reportnumber
'sitename' = $lines.SITE_NAME
}
New-Object -Type PSObject -Property $Prop
}
$FinalOutput
You would need to comment out the finaloutput lines earlier in your script.

Related

Powershell Looping through eventlog

I am trying to gather data from eventlogs of logons, disconnect, logoff etc... this data will be stored in a csv format.
This is the script i am working which got from Microsoft Technet and i have modified to meet my requirement. Script is working as it should be but there is looping going on which i can't figure out how it should be stopped.
$ServersToQuery = Get-Content "C:\Users\metho.HOME\Desktop\computernames.txt"
$cred = "home\Administrator"
$StartTime = "September 19, 2018"
#$Yesterday = (Get-Date) - (New-TimeSpan -Days 1)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = (Get-Date).AddDays(-1)
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server -Credential $cred
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "-"
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan
#End
First time when i run the script, it runs fine and i get the csv output as it should be. When i run the script again than a new CSV is created (as it should be) but the same event log enteries are created twice and run it again than three enteries are created for the same event. This is very strange as a new csv is created each time and i dont not have -append switch for export-csv configured.
$FilteredOutput = #()
$Output = #()
I did try adding these two lines in above script as i read somewhere that it is needed if i am mixing multiple variables into a array (i do not understand this so applogies if i get this wrong).
Can someone please help me this, more importantly, I need to understand this as it is good to know for my future projects.
Thanks agian.
mEtho
It sounds like the$Output and $FilteredOutput variables aren't getting cleared when you run the script subsequent times (nothing in the current script looks to do that), so the results are just getting appended to these variables each time.
As you've already said, you could add these to the top of your script:
$FilteredOutput = #()
$Output = #()
This will initialise them as empty arrays at the beginning, which will ensure they start empty as well as make it possible for them to be appended to (which happens at the script via +=). Without doing this on the first run the script likely failed, so I assume you must have done this in your current session at some point for it to be working at all.

Update different arrays via a common function

I'm currently making a Powershell script that will analyze multiple log files from a mail server to gather various statistics that will be stored in a number of different arrays.
I have the following code snippet as an example of updating one of the arrays.
#Update arrays
#Overall array
$objNewValue = New-Object -TypeName PSObject
$objNewValue = $PSOSSLOverall | Where-Object {($_.Version -contains $strVersion -and $_.Cipher -contains $strCipher -and $_.Bits -contains $strBits)}
If ($objNewValue -ne $null) {
try {
Write-Verbose "$strVersion $strCipher $strBits is already in the array, so we'll update TimeSeen value"
$objNewValue.TimesSeen++
$objNewValue.LastSeen = $dtTimestamp
} #try
catch {
Write-Host "Something whent wrong while attempting to update an existing object in the overall array" -BackgroundColor DarkRed
Write-Host "Current line: $strtemp[$i]"
Write-Host "Current values: $dtTimestamp <-> $strVersion <-> $strCipher <-> $strBits"
Write-Host "Current array:"
$PSOSSLOverall | Sort-Object -Property Version, Cipher -Descending | Format-Table -AutoSize
Write-Host "Exception object:"
$_
} #catch
} #If Check for existence in Overall array
Else {
try {
Write-Verbose "$strVersion $strCipher $strBits is not in the array, so it will be added "
$objNewValue = New-Object -TypeName PSObject
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'Version' -Value $strVersion
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'Cipher' -Value $strCipher
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'Bits' -Value $strBits
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'TimesSeen' -Value 1
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'Percentage' -Value 0
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'FirstSeen' -Value $dtTimestamp
Add-Member -InputObject $objNewValue -MemberType 'NoteProperty' -Name 'LastSeen' -Value $dtTimestamp
$PSOSSLOverall += $objNewValue
} #try
catch {
Write-Host "Something whent wrong while attempting to add a new object to the overall array"
Write-Host "Current line: $strtemp[$i]"
Write-Host "Current values: $dtTimestamp <-> $strVersion <-> $strCipher <-> $strBits"
Write-Host "Exception object:"
$_
} #catch
} #Else Check for existence in Overall array
However, when I have up to 10 or more arrays that I need to update, the result will be a lot of similar code as there's only relatively few lines that change each time - like the array being updated, the where clause, the variables used and number of columns in the arrays.
Would it be possible to create a function that can handle updating the different arrays?
Thanks in advance.
-Update-
To explain the code snippet above: All the variables are already set before this part of the script is run. $strtemp[$i] is actually where all the data comes from as that the current line in the log file from which I then extract the needed data and place it in various variables.
First I search the array in question, which in this case is $PSOSSLOverall, to see if the data from the current line is already in the array. If $objNewValue is not null, then the data is already there, and I then increment a counter and update a date stamp for that "row" of data. If $objNewValue is null, then the data is not already there, and then we added a now object (row) to the array with the data from various variables.
Each attempt is equipped with try/catch section for error handling.
The end result will be an array that looks like this (the percentage column is calculated elsewhere):
The other arrays have various number of columns, which I what I guess makes it difficult to make a common function to update them.
I have found myself in a similar situation once or twice. You'll have to refactor your code, converting inflexible static definitions into parameter variables. The idea is to separate the data from program logic so you can do something like pass a different set of attribute names and values into the same function for different circumstances.
Here were a couple places I found to improve flexibility:
Parameterize the Where-Object expressions used to find matching records so you don't write the same code for each combination of columns and values. See hasPropertyValues below. All it does is perform an -and concatenated -contains operation on each name-value pair you pass to it.
Parameterize the data you want changed for each eventuality. Your code does something when it finds matching records and when it finds no matching records. Pull those actions out of the script body and into an input parameter that can change when you're done working with the encryption dataset and have to move onto another. The UpdateRecords function takes hashtable parameters defining the shape of the data when new records are added and when matching records are found.
See below for the example. I think you can adapt some of the ideas in here to your code.
# this is a convenience function allowing us to test multiple name-value pairs against an object
function hasPropertyValues {
Param(
[object] $inputObject,
[hashtable] $properties
)
$result = $true
foreach($name in $properties.Keys){
$result = $result -and ($inputObject.$name -contains $properties[$name])
}
Write-Output $result
}
# this function evaluates each object in $inputDataset
# if an object matches the name-values defined in $matchProperties
# it updates the records according to $updateRecordProperties
# if no records are found which match $matchProperties
# a new object is created with the properties in both $matchProperties
# and $newRecordProperties
# All results are written to the pipeline, including unmodified objects
function UpdateRecords{
Param (
[object[]] $inputDataset,
[hashtable] $matchProperties,
[hashtable] $updateRecordProperties,
[hashtable] $newRecordProperties
)
$numberOfMatchingRecords = 0
foreach ($record in $inputDataset){
if ( hasPropertyValues -inputObject $record -properties $matchProperties) {
# a record with matching property values found.
# Update required attributes
$numberOfMatchingRecords++
foreach($name in $updateRecordProperties.Keys){
if ($updateRecordProperties[$name] -is 'ScriptBlock'){
# if the update is a scriptblock, we invoke the scriptblock
# passing the record as input. The result of the invocation
# will be set as the new attribute value
$newValue = & $updateRecordProperties[$name] $record
} else {
$newValue = $updateRecordProperties[$name]
}
$record | Add-Member -Force -MemberType NoteProperty -Name $name -Value $newValue
}
}
Write-Output $record
}
if ($numberOfMatchingRecords -eq 0) {
# no records found with the search parameters
$newRecord = New-Object -TypeName psobject -Property $newRecordProperties
foreach($key in $matchProperties.Keys){
$newRecord | Add-Member -MemberType NoteProperty -Name $key -Value $matchProperties[$key] -Force
}
Write-Output $newRecord
}
}
[object[]] $TestDataset= #(New-Object psobject -Property #{
version='TLSv1.2'
cipher='ECDHE-RSA-AES256-GCM-SHA384'
Bits=256
TimesSeen = 1833
Percentage = 87578
FirstSeen = [DateTime]::Now
LastSeen = [DateTime]::Now
})
function TestUpdateRecords{
$encryptionNewRecordDefaults = #{
TimesSeen = 1
Percentage = 0
FirstSeen = [DateTime]::Now
LastSeen = [DateTime]::Now
}
$encryptionUpdateAttributes = #{
LastSeen = [DateTime]::Now
TimesSeen = {$ARGS[0].TimesSeen + 1}
}
# test adding a new record
UpdateRecords -inputDataset $TestDataset `
-matchProperties #{ Version='TLSv1.0';cipher='unbreakable';bits=720} `
-updateRecordProperties $encryptionUpdateAttributes `
-newRecordProperties $encryptionNewRecordDefaults
# test modifying a matching record
UpdateRecords -inputDataset $things `
-matchProperties #{Version='TLSv1.2';cipher='ECDHE-RSA-AES256-GCM-SHA384';bits=256} `
-updateRecordProperties $encryptionUpdateAttributes `
-newRecordProperties $encryptionNewRecordDefaults
}
TestUpdateRecords
There are a lot of different ways to implement this kind of refactoring. You could, for instance, extract dataset-specific logic into scriptblocks and pass these to your main loop function.
Another possibility is to dig into the object-oriented features of PowerShell and try to build classes around each of your datasets. This could encapsulate the 'Update' and 'New' actions in a pleasant way. I'm not yet literate enough in powershell OO features to try.

Array of custom variable with two properties

I feel like my Delphi background is destroying my ability to figure this out. I'm trying to create an empty array (no data, just structure) in Powershell where each item has two properties. The end result would look something like this:
$WIP[0].signature = 'data'
$WIP[0].PID = 'data'
# other fake stuff in between
Write-host "Item 43 has signature: " $WIP[43].signature
For some reason, I'm roadblocking on every attempt to create what should be simple to do. Thoughts?
Update to answer questions
I know some people do similar to the following, but this isn't as flexible as I'd like:
$array = #()
$object = New-Object -TypeName PSObject
$object | Add-Member -Name 'Name' -MemberType Noteproperty -Value 'Joe'
$object | Add-Member -Name 'Age' -MemberType Noteproperty -Value 32
$object | Add-Member -Name 'Info' -MemberType Noteproperty -Value 'something about him'
$array += $object
This requires the values to be present for all three members when creating each $object. I was thinking the init would look more along the lines of (pseudocode):
$MyRecord = {
Signature as string
PID as integer
}
$RecArray = array of $MyRecord
That's notably a bad mashup of Delphi and Powershell. But would create a fully structured array, addressable as noted up top.
A PSv5+ solution that uses a PS class and a generic list ([System.Collections.Generic.List[]]) to store the instances (loosely speaking, an array that can grow efficiently).
# Your custom type.
class MyRecord {
[string] $Signature
[int] $PID
}
# If you want a list that can grow efficiently,
# use [System.Collections.Generic.List[]]
$RecList = [System.Collections.Generic.List[MyRecord]]::new()
# Add a new instance...
$RecList.Add([MyRecord]::new())
# ... and initialize it.
$RecList[0].Signature = 'sig1'
$RecList[0].Pid = 666
# ... or initialize it first, and then add it.
# Note the use of a cast from a hashtable with the property values.
$newRec = [MyRecord] #{ Signature = 'sig2'; PID = 667}
$RecList.Add($newRec)
# Output the list
$RecList
The above yields:
Signature PID
--------- ---
sig1 666
sig2 667
As for removing objects from the list:
To remove by index, use .RemoveAt(); an out-of-range index throws an error:
$RecList.RemoveAt(1)
To remove by object already stored in the list, use .Remove().
Note that the [bool] return value indicates whether the value was
actually removed (if the object wasn't in the list, the operation is
a no-op and $False is returned)
$actuallyRemoved = $RecList.Remove($newRec)
For details, see the docs.
You want to create a custom object.
You create an object that has all the properties you need. Then you create a collection, and you stuff instances of your new object into the collection. Here's an example:
$WIP = #()
$o = New-Object –TypeName PSObject
Add-Member -InputObject $o –MemberType NoteProperty –Name signature –Value 'foo'
Add-Member -InputObject $o –MemberType NoteProperty –Name data –Value 'bar'
$WIP += $o
$WIP[0].signature
$WIP[0].data
You'd need to execute the New-Object and Add-Member statements for each object you're creating.
So here's working example of how You can get something like this working:
$list=#()
1..100|foreach-object{
$obj=""|select signature,pid
$obj.signature="$_ signature"
$obj.pid="$_ PID"
$list+=$obj
}
With the object created this way - You can do $list[43].signature and it does work.
What exactly do you mean by "Dynamic"?
$array = #(
# Some type of loop to create multiple items foreach/for/while/dowhile
foreach ($item in $collection) {
New-Object psobject -Property #{
Signature = 'data'
PID = 'data'
}
}
)
Or you can manually add objects like so
$array = #()
# Later in code
$array += New-object psobject #{
Signature = 'data'
PID = 'data'
}
Then you can access each item like so:
$array[1].Signature
$array[1].PID
There is no real difference between this an what you have already been shown but I think this gives you what you are asking for (even though it is not the powershelly way to do things).
$object = "New-Object PSCustomObject -Property #{'Name' = ''; 'Age' = [int]}"
$array = 1..100 | %{Invoke-Expression $object}
$array[0].Name = 'Joe'
$array[0].Age = 12
$array[0]
You can use a hashtable with indices as keys, and your hashtable as values. It's pretty easy to work with.
$WIP = #{
0 = #{
signature = 'signature 0'
PID = 'PID 0'
}
1 = #{
signature = 'signature 1'
PID = 'PID 1'
}
}
You can add any index you want.
$WIP[12] = #{
signature = "signature 12"
PID = "PID 12"
}
$WIP[12].PID
# PID 12
You can initialize both, any, or none.
$WIP[76] = #{
signature = "signature 76"
}
$WIP[76].signature
# signature 76
$WIP[76].PID
# $null
Count gives you number of "active" elements.
$WIP.Count
# 4

Update object array over multiple iterations

I have an array of custom objects:
$report = #()
foreach ($person in $mylist)
{
$objPerson = New-Object System.Object
$objPerson | Add-Member -MemberType NoteProperty -Name Name -Value $person.Name
$objPerson | Add-Member -MemberType NoteProperty -Name EmployeeID
$objPerson | Add-Member -MemberType NoteProperty -Name PhoneNumber
$report += $objPerson
}
Note that I haven't set values for the last two properties. The reason I've done this is because I'm trying to produce a matrix where I'll easily be able to see where these are blanks (although I could just set these to = "" if I have to).
Then, I want to iterate through a second dataset and update these values within this array, before exporting the final report. E.g. (this bit is pretty much pseudo code as I have no idea how to do it:
$phonelist = Import-Csv .\phonelist.csv
foreach ($entry in $phonelist)
{
$name = $entry.Name
if ($report.Contains(Name))
{
# update the PhoneNumber property of that specific object in the array with
# another value pulled out of this second CSV
}
else
{
# Create a new object and add it to the report - don't worry I've already got
# a function for this
}
}
I'm guessing for this last bit I probably need my if statement to return an index, and then use that index to update the object. But I'm pretty lost at this stage.
For clarity this is a simplified example. After that I need to go through a second file containing the employee IDs, and in reality I have about 10 properties that need updating all from different data sources, and the data sources contain different lists of people, but with some overlaps. So there will be multiple iterations.
How do I do this?
I would read phonelist.csv into a hashtable, e.g. like this:
$phonelist = #{}
Import-Csv .\phonelist.csv | ForEach-Object { $phonelist[$_.name] = $_.number }
and use that hashtable for filling in the phone numbers in $report as you create it:
$report = foreach ($person in $mylist) {
New-Object -Type PSObject -Property #{
Name = $person.Name
EmployeeID = $null
PhoneNumber = $phonelist[$person.Name]
}
}
You can still check the phone list for entries that are not in the report like this:
Compare-Object $report.Name ([array]$phonelist.Keys) |
Where-Object { $_.SideIndicator -eq '=>' } |
Select-Object -Expand InputObject
I would iterate over the $phonelist two times. The first time, you could filter all phone entities where the name is in your $myList and create the desired object:
$phonelist = import-cse .\phonelist.csv
$report = $phonelist | Where Name -in ($mylist | select Name) | Foreach-Object {
[PSCustomObject]#{
Name = $_.Name
PhoneNumber = $_.PhoneNumber
EmployeeID = ''
}
}
The second time you filter all phone entities where the name is not in $myList and create the new object:
$report += $phonelist | Where Name -NotIn ($mylist | select Name) | Foreach-Object {
#Create a new object and add it to the report - don't worry I've already got a function for this
}

Powershell Custom object - not passing foreach variable

I'm trying to create a custom object based on server names from a text file.
The script I have goes and imports the txt file into a Variable. Then runs a foreach server in the servers variable to create the custom object. I would like to be able to output the object's properties as a table that doesn't include the header info each time.
See script and output below:
$SERVERS = gc c:\servers.txt
foreach ($srv in $SERVERS)
{
$Obj = New-Object PsObject -Property`
#{
Computername = $srv
SecurityGroup = (Get-QADComputer $srv).memberof
RebootDay = ((Get-QADComputer $srv).memberof).split(',').split(' ')[2]
Combined = ((Get-QADComputer $srv).memberof).split(',').split(' ').split('=')[1]
RebootTime = $obj.combined.substring(0,4)
}
echo $obj | ft Computername,RebootDay -autosize
}
This is the output currently:
Computername RebootDay
SERVER007 Sunday
Computername RebootDay
SERVER009 Sunday
Computername RebootDay
SERVER003 Sunday
I'd like it to look more like:
Computername RebootDay
SERVER007 Sunday
SERVER001 Sunday
SERVER009 Sunday
TessellatingHeckler was on the right track really. The issue with his code is that you can't pipe a ForEach($x in $y){} loop to anything (not to be confused with a ForEach-Object loop that you usually see shortened to just ForEach like $Servers | ForEach{<code here>}) You don't want to pipe objects to Format-Table one at a time, you want to pipe a collection of objects to it so that it looks nice. So here's the modified code:
$SERVERS = gc c:\servers.txt
$Results = foreach ($srv in $SERVERS)
{
New-Object PsObject -Property #{
Computername = $srv
SecurityGroup = (Get-QADComputer $srv).memberof
RebootDay = ((Get-QADComputer $srv).memberof).split(',').split(' ')[2]
Combined = ((Get-QADComputer $srv).memberof).split(',').split(' ').split('=')[1]
RebootTime = $obj.combined.substring(0,4)
}
}
$Results | FT ComputerName,RebootDay -auto
That collects the objects in an array, then you pass the whole array to Format-Table
Don't put the "ft" (Format-Table) command inside the loop, put it outside, once, at the end. e.g.
$SERVERS = gc c:\servers.txt
$results = foreach ($srv in $SERVERS)
{
$Obj = New-Object PsObject -Property`
#{
Computername = $srv
SecurityGroup = (Get-QADComputer $srv).memberof
RebootDay = ((Get-QADComputer $srv).memberof).split(',').split(' ')[2]
Combined = ((Get-QADComputer $srv).memberof).split(',').split(' ').split('=')[1]
RebootTime = $obj.combined.substring(0,4)
}
$Obj
}
$results | ft Computername,RebootDay -autosize
Edit: Fixed for foreach pipeline bug.
You could possibly neaten it a bit because you don't need to make a new PSObject for a hashtable, and then put the object into the pipeline; you don't need to repeat the Get-QADComputer commands three times. I'm suspicious that the $obj.combined line isn't doing anything - how can you refer to an object inside the properties of the new-object call, before it gets assigned that name? And the repeated splits could probably be combined because it operates on individual characters, not strings.
gc c:\servers.txt | foreach {
$memberof = (Get-QADComputer $_).memberof
#{
Computername = $_;
SecurityGroup = $memberof;
RebootDay = $memberof.split(', ')[2];
Combined = $memberof.split(', =')[1];
# ?? RebootTime = $obj.combined.substring(0,4)
}
} | ft Computername,RebootDay -autosize

Resources