Extracting array headers and keep their order - arrays

i wrote a function wich create an XLS file from an array. Everything works well but i'm facing a problem when extracting headers from my array.
My headers are organise alphabetically after extraction, but i want to keep them in the same order as my input array.
function DoTheMagicExcel {
[cmdletbinding()]
Param(
<# Array to work on #>
[Parameter( Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[Array]$MyArray,
<# Its Excel Sheet name #>
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[alias('Sheet')]
[string]$MySheetName,
<# Excel file name #>
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[ValidatePattern("\.(csv|xls)$")]
[alias('XlsFile')]
[string]$MyExcelFile
)
Begin {
<# Init them all #>
$excel = new-object -comobject Excel.Application
$excel.visible = $False
$excel.DisplayAlerts = $False
$XlsAlreadyExist = $False
if (Test-Path $MyExcelFile) {
Write-Output "Fichier Excel deja existant"
$XlsAlreadyExist = $true
$workbook = $excel.Workbooks.open("$PSScriptRoot\$MyExcelFile")
$workbook.Worksheets.Add() | Out-Null
$workbook.WorkSheets.item(1).Name = $MySheetName
$MySheet = $workbook.Worksheets.Item($MySheetName)
}
else {
$workbook = $excel.Workbooks.Add()
$workbook.WorkSheets.item(1).Name = $MySheetName
$MySheet = $workbook.Worksheets.Item($MySheetName)
}
$MyHeadercolumn = 1
$StartRow = 2
}
Process {
$MyArrayHeader = $MyArray | Get-member -MemberType 'NoteProperty' | Select-Object -ExpandProperty 'Name'
foreach ($header in $MyArrayHeader) {
$MySheet.cells.item(1, $MyHeadercolumn).font.bold = $true
$MySheet.cells.item(1, $MyHeadercolumn) = $header
$MyHeadercolumn++
}
foreach ($ThisRow in $MyArray) { ......
i mean this
$AllBcksummary[1] |ft -AutoSize
Server RG Environment Availibility Vault Status Regle Dernier Backup Type
------- -- ----------- ------------ ------ ------ ----- -------------- ----
SEFRAPB0106 RG_AXL production NOT SET backup Healthy Policy14512-BDay-Prod 24/01/2018 19:22:15 AppConsistent
$MyArrayHeader = $AllBcksummary | Get-member -MemberType 'NoteProperty' | Select-Object -ExpandProperty 'Name'
$MyArrayHeader |ft -AutoSize
Availibility
Coffre
Dernier Backup
Environment
Regle
RG
Serveur
Status
Type
Then when i create my Excel i no longer keep its original order
foreach ($header in $MyArrayHeader) {
$MySheet.cells.item(1, $MyHeadercolumn).font.bold = $true
$MySheet.cells.item(1, $MyHeadercolumn) = $header
$MyHeadercolumn++
}
if you have an idea ... :)
Thanks

Almost :)
$AllBcksummary.psobject.properties | select name
Name
----
Capacity
Count
IsFixedSize
IsReadOnly
IsSynchronized
SyncRoot
but adding index 0 do the trick !
$AllBcksummary[0].psobject.properties | select name
Name
----
Serveur
RG
Environment
Availibility
Coffre
Status
Regle
Dernier Backup
Type
Thanks guys

This should do the trick :)
$AllBcksummary.psobject.properties | select Name
From How to get powershell object properties in the same order that format-list does? ;)

Related

Get values from 2 arrays

Maybe the header is wrong but i dont know how to explain.
I have 4 csv files with aprox 15000 rows in each looking like this
number,"surname","forename","emailAddress","taxIdentifier"
100238963,"Smith","John","john.smith#gmail.com","xxxxxxxxxxxx"
Im reading in 9999 of the rows and creating a json file we use on a site to check every person, we then get a respond back for most of the users, and that respons is "number"
Then i need to find all them persons in the first array.
I have done it like this today, but it take to much time to check every person like this, is there any better way of doing this?
This is the code for getting the persons from the file and create json file:
$Files = Get-ChildItem -Path "$Folders\\*" -Include *.csv -Force
foreach ($File in $Files){
$fname = $file
$fname = (Split-Path $File.name -leaf).ToString().Replace(".csv", "")
$Savefile = $fname+ "_Cleaned.csv"
$users = Import-Csv $File
$body = "{`"requestId`": `"144x25`",`"items`": ["
$batchSize = 9999
$batchNum = 0
$row = 0
while ($row -lt $users.Count) {
$test = $users[$row..($row + $batchSize - 1)]
foreach ($user in $test) {
$nr = $user.number
$tax = $user.taxIdentifier
$body += "{`"itemId`": `"$nr`",`"subjectId`": `"$tax`"},"
}
And then this is the code to deal with the respons:
$Result = #()
foreach ($1 in $response.allowedItemIds)
{
foreach ($2 in $Users){
If ($2.number -like $1)
{
$Result += [pscustomobject]#{
number = $2.number
Surname = $2.surname
Forename = $2.forename
Email = $2.emailaddress
Taxidendifier = $2.taxIdentifier
}
}
}
}
$Result | Export-Csv -path "$folders\$savefile" -NoTypeInformation -Append
$row += $batchSize
$batchNum++
Hope someone has any ideas
Cheers
I think you can just do this:
# read the original data file
$originalCsv = #"
number,"surname","forename","emailAddress","taxIdentifier"
1000,"Smith","Mel","mel.smith#example.org","xxxxxxxxxxxx"
3000,"Wilde","Kim","kim.wilde#example.org","xxxxxxxxxxxx"
2000,"Jones","Gryff Rhys","gryff.jones#example.org","xxxxxxxxxxxx"
"#
$originalData = $originalCsv | ConvertFrom-Csv
# get a response from the api
$responseJson = #"
{
"requestId": "144x25",
"responseId": "2efb8b47-d693-46ac-96b1-a31288567cf3",
"allowedItemIds": [ 1000, 2000 ]
}
"#
$responseData = $responseJson | ConvertFrom-Json
# filter original data for matches to the response
$matches = $originalData | where-object { $_.number -in $responseData.allowedItemIds }
# number surname forename emailAddress taxIdentifier
# ------ ------- -------- ------------ -------------
# 1000 Smith Mel mel.smith#example.org xxxxxxxxxxxx
# 2000 Jones Gryff Rhys gryff.jones#example.org xxxxxxxxxxxx
# write the data out
$matches | Export-Csv -Path ".\myfile.csv" -NoTypeInformation -Append
I don't know if that will perform better than your example, but it should do as it's not got a nested loop that runs original row count * response row count times.

Can a PS Custom Object be created from a variable?

I have a 100 column table in sql server and I want to make it so not all of the columns need to be passed in the file to load. I have assigned column names in a table that then compares the columns in a hash table to find matching columns. I then create the code based on the match for the array I want to use to insert the data from the file. The problem is, it doesn't like calling the one variable to create the custom object.
I store the following below in a array. (up to a 100 of these, few below for sample (notice sqlcolumn2 is skipped for example)).
sqlcolumn1 = if ([string]::IsNullOrEmpty($obj.P1) -eq $true) {$null} else {"$obj.P1"}
sqlcolumn3 = if ([string]::IsNullOrEmpty($obj.P2) -eq $true) {$null} else {"$obj.P2"}
sqlcolumn4 = if ([string]::IsNullOrEmpty($obj.P3) -eq $true) {$null} else {"$obj.P3"}
sqlcolumn5 = if ([string]::IsNullOrEmpty($obj.P4) -eq $true) {$null} else {"$obj.P4"}
Here is the array:
foreach($line in $Final)
{
$DataRow = "$($line."TableColumnName") = if ([string]::IsNullOrEmpty(`$obj.$($line."PName")) -eq `$true) {`$null} else {`"`$obj.$($line."PName")`"}"
$DataArray += $DataRow
}
I then try to add it to a final array where I would want this to be looped through for each row of data after which I would perform the insert from the array. Even though the "string" value in the array above is correct if it were hand coded, I can't get it to recognize the rows and run.
foreach ($obj in $data2)
{
$test = [PSCustomObject] #{
$DataArray = Invoke-Expression $DataArray
}
If I just type $DataArray, it doesn't like this because it wants the = sign which I already have built into the string.
Is what I am trying to do even possible.
I was attempting to template out various different ways we receive this data, where some people send us 30 of the 100 columns, other more or less, and no one person using the exact columns to cut down on individual scripts for everything.
Adding more code:
Function ArrayCompare() {
[CmdletBinding()]
PARAM(
[Parameter(Mandatory=$True)]$Array1,
[Parameter(Mandatory=$True)]$A1Match,
[Parameter(Mandatory=$True)]$Array2,
[Parameter(Mandatory=$True)]$A2Match)
$Hash = #{}
foreach ($Data In $Array1) {
$Hash[$Data.$A1Match] += ,$Data
}
foreach ($Data In $Array2) {
$Hash[$Data.$A2Match] += ,$Data
}
foreach ($KeyValue In $Hash.GetEnumerator()){
$Match1, $Match2 = $KeyValue.Value.Where( {$_.$A1Match}, 'Split')
[PSCustomObject]#{
MatchValue = $KeyValue.Key
A1Matches = $Match1.Count
A2Matches = $Match2.Count
TablePosition = [int]$Match2.TablePosition
TableColumnName = $Match2.TableColumnName
# PName is the P(##) that is a generic ascending column value back to import-excel module. ColumnA = P1, ColumnB = P2 etc..until no data is detected. Allows flexibility and not having to know how many columns there are
PName = $Match1.Name}
}
}
$Server = 'ServerName'
$Catalog = 'DBName'
$DestinationTable = 'ImportIntoTableName'
$FileIdentifierID = 10
$FileName = 'Test.xlsx'
$FilePath = 'C:\'
$FullFilePath = $FilePath + $FileName
$data = Import-Excel -Path $FullFilePath -NoHeader -StartRow 1 # Import-
Excel Module for working with xlsx excel files
$data2 = Import-Excel -Path $ullFilePath -NoHeader -StartRow 2 # Import-
Excel Module for working with xlsx excel files
$ExpectedHeaderArray = #()
$HeaderArray = #()
$DataArray = #()
$HeaderDetect = #()
$HeaderDetect = $data | Select-Object -First 1 # Header Row In File
$HeaderDetect |
ForEach-Object {
$ColumnValue = $_
$ColumnValue |
Get-Member -MemberType *Property |
Select-Object -ExpandProperty Name |
ForEach-Object {
$HeaderValues = [PSCustomObject]#{
Name = $_
Value = $ColumnValue.$_}
$HeaderArray += $HeaderValues
}
}
# Query below provides a list of all expected file headers and the table
column name they map to
$Query = "SELECT TableColumnName, FileHeaderName, TablePosition FROM
dbo.FileHeaders WHERE FileIdentifierID = $($FileIdentifierID)"
$ds = Invoke-Sqlcmd -ServerInstance $Server -Database $Catalog -Query $Query
-OutputAs DataSet
$ExpectedHeaderArray = foreach($Row in $ds.Tables[0].Rows)
{
new-object psObject -Property #{
TableColumnName = "$($row.TableColumnName)"
FileHeaderName = "$($row.FileHeaderName)"
TablePosition = "$($row.TablePosition)"
}
}
#Use Function Above
#Bring it together so we know what P(##) goes with which header in file/mapped to table column name
$Result = ArrayCompare -Array1 $HeaderArray -A1Match Value -Array2 $ExpectedHeaderArray -A2Match FileHeaderName
$Final = $Result | sort TablePosition
foreach($Line in $Final)
{
$DataRow = "$($Line."TableColumnName") = if ([string]::IsNullOrEmpty(`$obj.$($Line."PName")) -eq `$true) {`$null} else {`"`$obj.$($Line."PName"))`"}"
$DataArray += $DataRow
}
# The output below is what the code inside the last array would be that I would use to import into excel.
# The goal is to be dynamic and match headers in the file to the stored header value and import into a table (mapped from header column to table column name)
# The reason for this is before I was here, there were many different "versions" of a layout that was given out. In the end, it is all one in the same
# but some send all 100 columns, some only send a handful, some send 80 etc. I am trying to have everything flow through here vs. 60+ pieces of code/stored procedures/ssis packs
Write-Output $DataArray
# Output Sample -- Note how in the sample, P2 and subsequent skip SQLColumn2 because P2 maps to the header value of position 3 in the sql table and each after is one off.
# In this example, SqlColumn2 would not be populated
# SqlColumn1 = if ([string]::IsNullOrEmpty($obj.P1) -eq $true) {$null} else {"$obj.P1"}
# SqlColumn3 = if ([string]::IsNullOrEmpty($obj.P2) -eq $true) {$null} else {"$obj.P2"}
# SqlColumn4 = if ([string]::IsNullOrEmpty($obj.P3) -eq $true) {$null} else {"$obj.P3"}
# SqlColumn5 = if ([string]::IsNullOrEmpty($obj.P4) -eq $true) {$null} else {"$obj.P4"}
# I know this doesn't work. This is where I'm stuck, how to build an array now off of this output from above
foreach ($obj in $data2)
{
$test = [PSCustomObject] #{
$DataArray = Invoke-Expression $DataArray}
}
I'm gong to re-state your question first, just to make sure I understand it properly (it's possible I don't!)...
You've got an excel file that looks something like this:
+---+---------+---------+---------+
| | A | B | C |
+---+---------+---------+---------+
| 1 | HeaderA | HeaderB | HeaderC |
+---+---------+---------+---------+
| 2 | Value P | Value Q | Value R |
+---+---------+---------+---------+
| 3 | Value S | Value T | Value U |
+---+---------+---------+---------+
You've also got a database table which looks like this:
+---------+---------+---------+---------+
+ ColumnW | ColumnX | ColumnY | ColumnZ |
+---------+---------+---------+---------+
+ ....... | ....... | ....... | ....... |
+---------+---------+---------+---------+
and a column mapping table like this (note, ColumnX isn't mapped in this example):
+-----------------+----------------+---------------+
| TableColumnName | FileHeaderName | TablePosition |
+-----------------+----------------+---------------+
| ColumnW | HeaderA | 1 |
+-----------------+----------------+---------------+
| ColumnY | HeaderB | 2 |
+-----------------+----------------+---------------+
| ColumnZ | HeaderC | 3 |
+-----------------+----------------+---------------+
You want to insert the values from the spreadsheet into the database table, using the data in your mapping table so you get this:
+---------+---------+---------+---------+
+ ColumnW | ColumnX | ColumnY | ColumnZ |
+---------+---------+---------+---------+
+ Value P | null | Value Q | Value R |
+---------+---------+---------+---------+
+ Value S | null | Value T | Value U |
+---------+---------+---------+---------+
So let's load the spreadsheet (letting the header row generate meaningful property names this time):
$data = Import-Excel -Path ".\MySpreadsheet.xlsx";
write-host ($data | ft | out-string);
# HeaderA HeaderB HeaderC
# ------- ------- -------
# Value P Value Q Value R
# Value S Value T Value U
and get your column mapping data (I'm programmatically creating an in-memory dataset, but you obviously read yours from your database instead):
$mappings = new-object System.Data.DataTable;
$null = $mappings.Columns.Add("TableColumnName", [string]);
$null = $mappings.Columns.Add("FileHeaderName", [string]);
$null = $mappings.Columns.Add("TablePosition", [int]);
#(
#{ "TableColumnName"="ColumnW"; "FileHeaderName"="HeaderA"; "TablePosition"=1 },
#{ "TableColumnName"="ColumnY"; "FileHeaderName"="HeaderB"; "TablePosition"=2 },
#{ "TableColumnName"="ColumnZ"; "FileHeaderName"="HeaderC"; "TablePosition"=3 }
) | % {
$row = $mappings.NewRow();
$row.TableColumnName = $_.TableColumnName;
$row.FileHeaderName = $_.FileHeaderName;
$row.TablePosition = $_.TablePosition;
$mappings.Rows.Add($row);
}
$ds = new-object System.Data.DataSet;
$ds.Tables.Add($mappings);
write-host ($ds.Tables[0] | ft | out-string)
# TableColumnName FileHeaderName TablePosition
# --------------- -------------- -------------
# ColumnW HeaderA 1
# ColumnY HeaderB 2
# ColumnZ HeaderC 3
Now we can build the "mapped" objects:
$values = #();
foreach( $row in $data )
{
$properties = [ordered] #{};
foreach( $mapping in $mappings )
{
$properties.Add($mapping.TableColumnName, $row."$($mapping.FileHeaderName)");
}
$values += new-object PSCustomObject -Property $properties;
}
write-host ($values | ft | out-string)
# ColumnW ColumnY ColumnZ
# ------- ------- -------
# Value P Value Q Value R
# Value S Value T Value U
The tricksy bit is $properties.Add($mapping.TableColumnName, $row."$($mapping.FileHeaderName)"); - basically, you can access object properties in PowerShell using a dotted string literal or variable (I'm not sure of the exact feature name) - e.g.
PS> $myValue = new-object PSCustomObject -Property #{ "aaa"="bbb"; "ccc"="ddd" }
PS> $myValue."aaa"
bbb
PS> $myProperty = "aaa"
PS> $myValue.$myProperty
"bbb"
so $row."$($mapping.FileHeaderName)" is an expression that evaluates to the value of the property of $row named in $mapping.FileHeaderName.
And then finally you can insert the objects into your database using your existing process...
Note that I couldn't quite work out what your ArrayCompare is actually doing so it's possible the above doesn't solve your problem 100%, but it's hopefully close enough that you can either work the difference out yourself, or leave a comment with where it differs from your desired solution.
Hope this helps.

How to combine and rename properties of arrays in powershell?

I am pulling data from an API that ConnectWise offers. I am trying to pull a list of usernames and password and put them in to a format which I can either reference in another script (or combiner with this one), or that I can export to a CSV. I'd prefer the first if possible.
The code:
Function Get-CWConfiguration
{
[string]$BaseUri = "$CWServerRoot"
[string]$Accept = "application/vnd.connectwise.com+json; version=v2015_3"
[string]$ContentType = "application/json"
[string]$Authstring = $CWInfo + '+' + $CWCredentials1 + ':' + $CWCredentials2
$encodedAuth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(($Authstring)));
$Headers=#{"Authorization"="Basic $encodedAuth"}
Invoke-RestMethod -URI $BaseURI -Headers $Headers -ContentType $ContentType -Method Get
}
$username = (Get-CWConfiguration).questions | Where-Object {($_.questionid -eq 419)} | Sort-Object -Property questionid | select answer
$password = (Get-CWConfiguration).questions | Where-Object {($_.questionid -eq 420)} | Sort-Object -Property questionid | select answer
Everything above the $username variable is the stuff connecting to the API and I did not modify that. The code below is the best way I found to pull the info that I need. This is returning each variable as an array, which is fine but if that happens I need to have an array with 2 columns. 1 named Username and the other named Password. if I do a $username | Get-Member it returns:
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
answer NoteProperty string answer=admin#admin.com
My end goal is to have a variable like $credentials output something that displays like:
Username Password
-------- ----------
admin1#admin.com Password1
admin2#admin.com Password2
admin3#admin.com Password3
Most methods I have tried have resulted in everything put output to 1 line or 1 column. I'm not sure if I should be combining the arrays and renaming the $username.answer and $password.answer fields or creating a PSObject? My main difficulty has been that both variables have that same answer NoteProperty
You'd want to create new objects with the expanded properties of the current objects. This could all look something like this (note that I'm manually creating analogs for what you're all dealing with and ignoring the string manipulation you'll likely have to do)
$Username = New-Object -TypeName pscustomobject -Property #{Answer = "Answer:Hithisispatrick"}
$Password = New-Object -TypeName pscustomobject -Property #{Answer = "Answer:HelloThisisSquidward"}
$UsernameAnswer = $Username | Select -ExpandProperty Answer
$PasswordAnswer = $Password | Select -ExpandProperty Answer
$Final = New-Object -TypeName pscustomobject -Property #{UserName= $UsernameAnswer ; Password = $PAsswordAnswer}
This should give you output something like this:
UserName Password
-------- --------
Answer:Hithisispatrick Answer:HelloThisisSquidward
Then if you want to parameterize it/Expand it, you should be able then to toss it in to a foreach loop and have it output the Custom Object, setting that equal to an array that then builds out of those objects, like so(I'll use while to make it simpler to show):
$Count = 0
$Username = New-Object -TypeName pscustomobject -Property #{Answer = "Answer:Hithisispatrick"}
$Password = New-Object -TypeName pscustomobject -Property #{Answer = "Answer:HelloThisisSquidward"}
$Array = While ($Count -lt 5){
$UsernameAnswer = $Username | Select -ExpandProperty Answer
$PAsswordAnswer = $Password | Select -ExpandProperty Answer
$Final = New-Object -TypeName pscustomobject -Property #{UserName= $UsernameAnswer ; Password = $PAsswordAnswer}
$Final
$Count += 1
}
$Array
This produces something like this:
Password UserName
-------- --------
Answer:HelloThisisSquidward Answer:Hithisispatrick
Answer:HelloThisisSquidward Answer:Hithisispatrick
Answer:HelloThisisSquidward Answer:Hithisispatrick
Answer:HelloThisisSquidward Answer:Hithisispatrick
Answer:HelloThisisSquidward Answer:Hithisispatrick

Powershell Select-Object -expandproperty strange behavior when input is array and contains array properties

I am trying to build a report file collecting data from various sources.
I have built a reporting structure like this:
$Data = import-csv "some CSV FILE"
<#
csv file must look like this
hostname,IP
server1,192.168.1.20
#>
Then I am building an array object, prepopulated with "initial values", and I attach it to my $data variable
$Ids = ('7.1.1.1','7.1.1.2')
$CheckObj= #()
foreach ($id in $IDs) {
$row = "" | Select-Object CheckID,CheckData,CheckDataRaw
$row.CheckID = $id
$row.CheckData = "NotChecked"
$CheckObj+= $row
}
$Data = $Data | Select *,CheckData
$data | % {$_.CheckData = $CheckObj}
The resulting object is:
hostname : server1
ip : 192.168.1.20
CheckData : {#{CheckID=7.1.1.1; CheckData=NotChecked; CheckDataRaw=},
#{CheckID=7.1.1.2; CheckData=NotChecked; CheckDataRaw=}}
All is well until I want to do this:
$FinalReport = $data | Select-Object -Property * -ExpandProperty Checkdata
I get all these errors, which let's say I can ignore...
Select-Object : The property cannot be processed because the property
"CheckData" already exists.
At line:1 char:24
+ ... lReport = $data | Select-Object -Property * -ExpandProperty Checkdata
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (#{hostname=serv...ystem.Objec
t[]}:PSObject) [Select-Object], PSArgumentException
+ FullyQualifiedErrorId : AlreadyExistingUserSpecifiedPropertyExpand,Micro
soft.PowerShell.Commands.SelectObjectCommand
BUT, an entire set of other variable gets altered, like:
$data | fl
hostname : server1
ip : 192.168.1.1
CheckData : {#{CheckID=7.1.1.1; CheckData=NotChecked; CheckDataRaw=;
hostname=server1; ip=192.168.1.1}, #{CheckID=7.1.1.2;
CheckData=NotChecked; CheckDataRaw=; hostname=server1;
ip=192.168.1.1}}
aswell as the $CheckObj variable
$CheckObj
CheckID : 7.1.1.1
CheckData : NotChecked
CheckDataRaw :
hostname : server1
ip : 192.168.1.1
CheckID : 7.1.1.2
CheckData : NotChecked
CheckDataRaw :
hostname : server1
ip : 192.168.1.1
This is totally unintended on my side...
Can someone clarify what I am doing wrong?
I am using powershell 5.0 on Windows 7.
All testing was done using powershell_ise, and I didn't change any of the powershell defaults
My expected result would be for the $Final Report variable to contain the expanded content, not all the variables I used in the process...
It seems, after a bit more digging I understood, to some extent why this is occurring.
I am using simple $b = $a assignments, which appear to be a form of shallow copy. So any change in $b also impacts object $a and vice-versa.
For my purpose I need distinct copies of the data, it seems the solution is to do a deep copy, similar to the solution of this post:
PowerShell copy an array completely
So the working code, which gives me the desired result would be:
Function Copy-Object ($Source,[switch]$DeepCopy) {
# Serialize and Deserialize data using BinaryFormatter
if ($DeepCopy) {
$ms = New-Object System.IO.MemoryStream
$bf = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
$bf.Serialize($ms, $Source)
$ms.Position = 0
#Deep copied data
$Target = $bf.Deserialize($ms)
$ms.Close()
Write-Output $Target
}
Else {
Write-Output $Source
}
}
$Data = "" | select hostname,IP
$data.Hostname = "server1"
$data.IP = "192.168.1.10"
$Ids = ('7.1.1.1','7.1.1.2')
$CheckObj= #()
foreach ($id in $IDs) {
$row = "" | Select-Object CheckID,CheckData,CheckDataRaw
$row.CheckID = $id
$row.CheckData = "NotChecked"
$CheckObj += $row
}
$Data = Copy-Object -source $Data -DeepCopy | Select *,CheckData2
$Data | % {$_.CheckData2 = Copy-Object -source $CheckObj -DeepCopy}
$FinalReport = Copy-Object -source $Data -DeepCopy | Select-Object -Property hostname,IP -ExpandProperty Checkdata2
$FinalReport | ft
output being:
CheckID CheckData CheckDataRaw hostname IP
------- --------- ------------ -------- --
7.1.1.1 NotChecked server1 192.168.1.10
7.1.1.2 NotChecked server1 192.168.1.10

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