Two row header for Get-ADObject | Export-Csv? - powershell

An external system we use requires an import of an excel template. The data that is being imported can be derived from Active Directory data.
I am building a one-click solution in PowerShell 2.0 that builds this template out with current Active Directory data.
The current process I am using is Get-ADObject > select and do some reformating | Export-Csv.
The template I am trying to replicate has two header rows. Export-Csv only provides one header, based on the field name (I actually used Select-Object #{Label=,Expression=} to customize the ADObject property names).
How can I add another header row? Can I append an entry to the beginning of each ADObject property?
Header 1
Entry1
Entry2
Entry3
to
Header 1
Header 2
Entry1
Entry2
Entry3

Maybe just duplicate to 2nd header?
Get-ADObject | Select-Object Header1,#{label='Header2';expression=$_.Header1}
Or if it needs to be in same row:
$Data = Get-ADObject | additional piping
$Data.Header1 += 'Header2'
$Data | Export-CSV
Or you are trying to add Property
$Data = Get-ADObject | additional piping
[PSCustomObject]$NewData = $Data
$NewData | Add-Member -MemberType NoteProperty -Name 'MyProperty' -Value 'myvalue'
$NewData | Export-CSV

So I ended up getting what I wanted, by first converting my AD Object into a Custom object and creating second custom object with the same fields, but my headers as values. Threw them both into an array, and it writes beautifully.
$ad = Get-AdObject...
$obj1 = New-Object -TypeName PSCustomObject -property $ad
$hash = #{
'col1'='header1';
'col2'='header2';
...
}
$obj2 = New-Object -TypeName PSCustomObject -property $hash
$arr1 = #()
$arr1 += $obj2, $obj1
$arr1 | Export-csv -path...

Related

How to specify column position when adding new column to a csv file using PowerShell

How can I specify the column position when adding a new column to an existing csv file?
I want to add the new column as second column (Not at the end what the default is).
The first column is always the same, but the other columns can differ per file (so it is not known on beforehand which columns (names and order) there are (with the exception of the first column, which always contains the name)).
As far as I know there is no position parameter for Add-Member.
Sample code:
$csv = Import-Csv -Path "attendees.csv" -Delimiter ';'
foreach ($row in $csv)
{
$row | Add-Member -'GUID' -Value (New-Guid).Guid -MemberType NoteProperty
}
$csv | Export-Csv new_attendees.csv' -NoTypeInformation
In case you do not know the column names at forehand.
Using Select-Object with a calculated property for this:
$csv = Import-Csv -Path "attendees.csv" -Delimiter ';'
$Properties = [Collections.Generic.List[Object]]$csv[0].psobject.properties.name
$Properties.Insert(1, #{ n='Guid'; e={ New-Guid } }) # insert at column #1
$csv |Select-Object -Property $Properties |Export-Csv new_attendees.csv' -NoTypeInformation
Explanation: (Updated 2022-11-12)
Each object PowerShell has een hidden PSObject property where you can dynamically access information about the property as e.g. its name.
Using the PowerShell Member-Access enumeration feature will list all the psobject.properties.name as an array of scalar strings.
I am using just the first object $csv[0] to determine the property (column) names as I do not want to choke the PowerShell pipeline and continue to support one-at-a-time processing. In other words, I presume that the following objects have unified property names. Any well written PowerShell cmdlet follows the strongly encouraged development guideline to implement for the middle of a pipeline
Thanks to the impliciet .Net conversion, it is easy to type cast the PowerShell Array (of property names) to the Collections.Generic.List[Object] type
Which happens to have a List<T>.Insert(Int32, T) Method. This lets you insert a item (in this case an object) at a certain position (in this case: 1)
Note that this method is The zero-based
The -Property parameter of the Select-Object cmdlet, doesn't just support an ordered list of property names but also calculated properties which is used here to create complete property along with its name, value (expression) in the form of:
#{ n='Guid'; e={ New-Guid } }
I don't think there's an easy way to insert a property into an object in-place at a specific index, but here's a quick proof-of-concept I knocked out to create a new object based on the original...
It could do with some error handling and perhaps some parameter attributes to support the pipeline, but it basically works...
function Insert-NoteProperty
{
param(
[pscustomobject] $InputObject,
[string] $Name,
[object] $Value,
[int] $Index
)
$properties = #($InputObject.psobject.Properties);
$result = [ordered] #{};
# append the properties before the index
for( $i = 0; $i -lt $Index; $i++ )
{
$result.Add($properties[$i].Name, $properties[$i].Value);
}
# append the new property
$result.Add($Name, $Value);
# append the properties after the index
for( $i = $Index; $i -lt $properties.Length; $i++ )
{
$result.Add($properties[$i].Name, $properties[$i].Value);
}
return [pscustomobject] $result;
}
Example:
$original = [pscustomobject] [ordered] #{ "aaa"="bbb"; "ccc"="ddd" }
$original
# aaa ccc
# --- ---
# bbb ddd
$updated = Insert-NoteProperty `
-InputObject $original `
-Name "new" `
-Value "value" `
-Index 1;
$updated
# aaa new ccc
# --- --- ---
# bbb value ddd
You can use this with a csv file as follows:
$csv = #"
aaa,ccc
bbb,ddd
"#
$data = $csv | ConvertFrom-Csv;
$newdata = $data | foreach-object {
Insert-NoteProperty `
-InputObject $_ `
-Name "new" `
-Value "value" `
-Index 1
}
$newcsv = $newdata | ConvertTo-Csv
$newcsv
# "aaa","new","ccc"
# "bbb","value","ddd"
Add-Member does not let you specify at which position to add a property to an object. So you have to build a new object where you can control the position of the property, e.g.:
$newObjects = #(
foreach ($row in $csv){
$attrsHt = [ordered]#{
[propertyName]=[propertyValue]
Guid=([guid]::NewGuid()).guid
[propertyName]=[propertyValue]
[propertyName]=[propertyValue]
}
New-Object -TypeName psobject -Property $attrsht
}
)
Or you can use select-object to change the order of the output:
foreach ($row in $csv)
{
$row | Add-Member -Name 'GUID' -Value (New-Guid).Guid -MemberType NoteProperty
}
$csv | select-object [propertyName],Guid,[propertyName],[propertyName] | export-csv new_attendees.csv' -NoTypeInformation
Ok as the propertyNames are unknown/dynamic you could do:
#Get the csv header and split by delimiter to get array of property names
[System.Collections.ArrayList]$csvHeader = (get-content [path])[0] -split ","
#Insert Guid at 2nd Position
$csvHeader.insert(1,'GUID')
$csv | select-object $csvHeader | export-csv new_attendees.csv -NoTypeInformation

Building a custom object in powershell (Add-Member)

Trying to build a custom object inside a For-EachObject loop.
Here is the code
$infouser = New-Object -TypeName psobject
(Get-ChildItem -Path "\\$poste\C$\Users").Name | ForEach-Object {
$nomcomplet += Get-ADUser -Identity $_ | select-object -ExpandProperty userprincipalname
Add-Member -InputObject $infouser -Name "Code" -Value $_ -MemberType NoteProperty
Add-Member -InputObject $infouser -Name "Nom complet" -Value $nomcomplet -MemberType NoteProperty
}
$infouser | Out-GridView
What i'm trying to achieve is a custom object containing the usernames in C:\USERS along with their equivalent full e-mail adress from the AD.
What I have works partially, it displays the first one it can add, but it doesn't "append" the others :
Giving the error : "Add-Member : Cannot add a member with the name...because a member with that name already exists. If you want to overwrite the member anyway, use the Force parameter to overwrite it."
I don't want to overwrite, I want to append it to the object so the final object contains all usernames and all adresses.
What am I doing wrong here? Thanks
You need to create an array of psobjects, not a single psobject. You're creating a single object and re-adding the properties X times.
This implicitly creates an array and adds a new psobject for each loop.
$infouser = (Get-ChildItem -Path "\\$poste\C$\Users").Name | ForEach-Object {
[pscustomobject]#{
'Code' = $_
'Nom Complet' = $(Get-ADUser -Identity $_ | select-object -ExpandProperty userprincipalname )
}
}
$infouser | Out-GridView

Split property value in Powershell

I am currently trying to do an Out-GridView to get a simple overview about our group policy objects. To do so, I am using the Get-GPO cmdlet, like so:
Get-GPO -all |
Select-Object -Property DisplayName, Description |
Sort-Object -Property DisplayName |
Out-GridView
In our company we use the first line of the description field to store the name of the admin who created the policy, and all following lines hold a short description.
I would want to be able to grab the first line of the the Description field with the column header Responsability and all other lines of the field in a separate column. So assuming my current code would give me a table like this:
DisplayName | Description
-------------------------
GPO1 | Username
| stuff
| stuff
I would want it to look like this:
DisplayName | Responsability | Description
------------------------------------------
GPO1 | Username | stuff
| | stuff
How can I achieve this?
As #Matt suggested, you can use a calculated property.
Then since Description is a string, rather than an array of strings, you will need to split the line at the line breaks. This can be done by using -split and since it's information from a GPO we can assume Windows line endings `r`n (Otherwise you could use [environment]::newline)
The first property, use array element [0] will be the first line. For the second property, we'll need to save the array in a variable. Then we can use the length of that variable to get first element through the last.
Get-GPO -all |
Select-Object -Property DisplayName, #{
Name = "Responsibility"
Expression = {($_.Description -split "`r`n")[0]}
}, #{
Name = "Description"
Expression = {
$linearray = ($_.Description -split "`r`n")
$linearray[1..($linearray.length - 1)] | Out-String
}
} |
Sort-Object -Property DisplayName |
Out-GridView
Alternatively, you could create a new object rather than using the calculated property.
Get-GPO -all |
ForEach-Object {
$linearray = ($_.Description -split "`r`n")
[pscustomobject]#{
"DisplayName" = $_.DisplayName
"Responsibility"= $linearray[0]
"Description" = $linearray[1..($linearray.length - 1)] | Out-String
}
} |
Sort-Object -Property DisplayName |
Out-GridView
The first thing to understand is what Get-GPO is returning: an array of objects, each of which has a set of properties.
What is displayed in your table is a series of rows (one per object), with the columns being the values of the properties for that object.
Therefore if you want a new column, you need a new property.
There are two ways you can do this: create a calculated property with Select-Object or add a property to the objects via Add-Member.
Calculated
You may provide a hashtable as a property to Select-Object, and the hashtable must have two keys:
Name (the name of the property)
Expression (a scriptblock that will be executed to determine the value, where $_ refers to the object itself)
Get-GPO -all |
Select-Object -Property DisplayName, Description, #{
Name = 'Responsibility'
Expression = {
($_.Description -split '\r?\n')[0] # First line
}
} |
Sort-Object -Property DisplayName |
Out-GridView
New Member
You can use a ScriptProperty that will execute a scriptblock each time the property is called on the object. Use $this to refer to the object in this context.
Get-GPO -all |
Add-Member -MemberType ScriptProperty -Name Responsibility -Value {
($this.Description -split '\r?\n')[0] # First line
} -Force -PassThru |
Select-Object -Property DisplayName, Responsibility, Description |
Sort-Object -Property DisplayName |
Out-GridView
I would probably use something like this:
Get-GPO -All | ForEach-Object {
$info = $_.Description
$pos = $info.IndexOf([Environment]::NewLine)
if ( $pos -gt 0 ) {
$responsibility = $info.Substring(0,$pos)
$description = $info.Substring($pos + [Environment]::NewLine.Length)
}
else {
$responsibility = ""
$description = $info
}
[PSCustomObject] #{
"DisplayName" = $_.DisplayName
"Responsibility" = $responsibility
"Description" = $description
}
}
This way you can preserve formatting.

Force Convertto-html to only include an object's "MemberType" "property"

I have an array of system.data.datarow objects. Now the properties of these objects have the info I want. When I pass these objects to convertto-html though it picks up all this extra crap and I see rows with names like RowError,RowState,Table- when all I want is the objects properties.
Is there a way I can only include the object's properties to be converted to html (meaning if I do a Get-Member on the object the "MemberType" property).
Can I convert these objects to generic psobjects without having to loop through them all and rebuild them with New-Object?
You can grab the names of all the Property-type properties with Get-Member and Select-Object:
$Props = Get-Member -InputObject $DataRowObjects[0] -MemberType Property | Select-Object -ExpandProperty Name
$Html = $DataRowObjects | Select-Object -Property $Props | ConvertTo-Html
The only problem with the accepted answer is that it changes the order of the fields to alphabetical from the given field names
To avoid and keep the original order from the select statement, I used the following approach
$unwantedColumns = #('RowError','RowState','Table','ItemArray','HasErrors') # these guys get added automatically by Invoke-SqlCmd
$props = $sqlResult.PSObject.Properties.Name | Where-Object {$_ -NotIn $unwantedColumns}
$htmlBody = $sqlResult |
Select-Object -Property $props |
ConvertTo-Html #ConvertToHtmlArgs |
Out-String

Combining variables in a table

I am trying to combine two variables each containing a list of values:
cls
$Sites = Get-ADReplicationSite -Filter *
$Subnets = Get-ADReplicationSubnet -Filter *
$a = New-Object PSObject
$a | add-member Noteproperty "Site" $Sites.Name
$a | add-member Noteproperty "Subnet" $Subnets.Name
$a | format-table
My output looks like this:
Site Subnet
---- ------
{Default-First-Site-Name, SITE1, SI... {10.0.0.0/24, 20.0.0.0/24, 30.0.0.0/...
As the above does not result in a clear table I wonder where I went wrong. Preferably I would combine these two variables into a .csv file. However I am not sure on how I would give each list a Header before piping it to the Export-CSV cmdlet.
Assuming that the number of sites is equal to the number of subnets, try this:
$sites | Foreach {$i=0}{new-object pscustomobject -prop #{Site=$_;Subnet=$subnets[$i]}; $i++} | Format-Table