Split property value in Powershell - 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.

Related

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

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...

Powershell - build a custom object with an unknown number of columns

I need to create a CSV that contains all possible emails addresses that an Active Directory user has. The CSV must be in the following format (very rigid API for where their going to be imported to):
Username | EmailAddress1 | EmailAddress2 | EmailAddressN
My script so far looks like this:
$Group = "GroupNAme
$usersObj = #()
$countMax = 0
$GetAdGroup = Get-AdGroup -Identity $Group -Properties *
[array]$members = $GetAdGroup.Members
ForEach ($member in $members) {
$currentUser = get-aduser -Identity $member `
-Properties EmailAddress, ProxyAddresses |
Where {$_.Enabled -eq "True"}
$countTemp = ($currentUser.ProxyAddresses).count
if ($countTemp -gt $countMax){ $countMax = $countTemp}
foreach ($mailAdd in $currentUser.ProxyAddresses) {
$usersOBJ += [pscustomobject]#{
'Username' = $currentUser.SamAccountName;`
'ProxyAddresses' = $mailAdd.SubString(5)
}
}
$usersOBJ | Export-CSV -NoTypeInformation C:\Export.csv
Now my existing Object spits out the Users as follows:
UserName | Emailaddress1
Username | Emailaddress2
Username | EmailsaddressN
I can't seem to make the leap into working out how to create a better object. I can get the max number of ProxyAddresses that occur but I'm not sure how to figure that into building my object and then populating the values of the $currentUser.ProxyAddresses into those columns.
I've read about Hash tables but they don't seem to fit my requirements, usually taking the form:
Username1 | Username2
Emailaddress1 | Emailaddress1
Emailaddress2 | Emailaddress2
Can anyone please point me in the right direction?
Cheers!
You just need to separate the values you're adding a bit. Add the username to a single object before the loop, then add additional properties on an as-needed basis. Finally, add the object to your array to be exported.
Try this:
...
$x = New-Object System.Object
$x | Add-Member –type NoteProperty –Name UserName –Value $currentUser.SamAccountName
$i=0
foreach ($mailAdd in $currentUser.ProxyAddresses) {
$i++
$x | Add-Member –type NoteProperty –Name "Emailaddress$i" –Value $mailAdd.SubString(5)
}
$usersOBJ += $x
Assuming you add all the users to $usersOBJ before exporting the csv, the columns should work perfectly for any number of proxy addresses.

Exchange Get-MailboxFolderStatistics FolderSize to MB

Morning folks, and what a sad day it is to be British.
Anyway, I'm trying to get MailboxFolderStatistics's FolderSize to MB.
The following line:
Get-MailboxFolderStatistics Joe.Bloggs |
Where-Object { $_.FolderPath -ne "/Deletions" } |
Select-Object FolderPath, #{ N = "FolderSize (MB)"; E = { $_.FolderSize.ToMB() } }
works fine when I'm using Exchange Management Shell.
But if I'm using a remote PS session into one of my Exchange boxes, I don't get anything for FolderSize.
Any ideas?
It's because the Exchange Management Shell you run on the server includes a type named Microsoft.Exchange.Data.ByteQuantifiedSize that gets converted to a System.String through remoting. The former exposes a ToMB() method, the latter does not.
I have written a workaround, but maybe there is a simpler and/or prettier method :
Get-MailboxFolderStatistics Joe.Bloggs |
Where-Object { $_.FolderPath -ne "/Deletions" } |
Select-Object FolderPath, #{
N = "FolderSize (MB)";
E = {
"{0:N2}" -f ((($_.FolderSize -replace "[0-9\.]+ [A-Z]* \(([0-9,]+) bytes\)","`$1") -replace ",","") / 1MB)
}
}
This uses a regular expression to turn the ugly string (example : 3.712 KB (3,801 bytes)) into a usable number. On my system , is not a valid digit grouping symbol so I had to remove it from the string, too.
You can use the following lines to get the $_.FolderSize represented in [decimals]
Select-Object #{
N = "FS_MB";
E = {
[math]::round( ([decimal](($_.FolderSize -replace "[0-9\.]+ [A-Z]* \(([0-9,]+) bytes\)","`$1") -replace ",","") / 1MB),2)
}
}
Typically when looking at folder sizes there is desire to sort them by size descending. To achieve this we need to know the FolderAndSubfolderSize in Bytes and store that in a bigint property as opposed to System.String. It's not rocket science to convert Bytes to Kb,Mb,Gb so I won't go into that here.
In-line syntax for dynamically adding a new property FolderAndSubfolderSizeBytes (I've used backticks to split over several lines for legibility only)
Get-EXOMailbox -Identity user.name#domain.com`
| Get-EXOMailboxFolderStatistics `
| Select Name,FolderAndSubfolderSize,#{`
name="FolderAndSubfolderSizeBytes";`
expression={((($_.FolderAndSubfolderSize -replace '^(.*\()(.*)(\sbytes\))$','$2').Replace(',','')) -as [bigint])}} `
| Sort-Object -Property FolderAndSubfolderSizeBytes -Descending | ft
Long-hand add new properties to variable object for later re-use
$mb = Get-EXOMailbox -Identity user.name#domain.com | Get-EXOMailboxFolderStatistics
foreach ($folder in $mb) {
$folder | Add-Member -NotePropertyName FolderSizeBytes -NotePropertyValue ((($folder.FolderSize -replace '^(.*\()(.*)(\sbytes\))$','$2').Replace(',','')) -as [bigint])
$folder | Add-Member -NotePropertyName FolderAndSubfolderSizeBytes -NotePropertyValue ((($folder.FolderAndSubfolderSize -replace '^(.*\()(.*)(\sbytes\))$','$2').Replace(',','')) -as [bigint])
}
$mb | Select Name,FolderPath,FolderAndSubfolderSize,FolderAndSubfolderSizeBytes | Sort-Object -Property FolderAndSubfolderSizeBytes -Descending | ft

Check the values of an dynamic object properties

I have created an object as part of a customer import. Now each customer object has a dynamic set of properties that all with have the prefix: "Cust." . I just need to loop through these properties and first get the name of the property and secondly, get the value.
$customerOptions = $Customer | Select-Object -property "Cust.*"
Since there's not much info about what $Customer is and what you want to use, I'll be guessing that $Customer is a single object and not an array. To loop through the properties starting with Cust. while being able to access both property-name and property-value, I would use this. Customize it to your needs:
$Customer = [pscustomobject]#{ Name = "Object1"; "Cust.ID" = 1; "Cust.Car" = "Mercedes"}
$Customer.psobject.Properties | Where-Object { $_.Name -like 'Cust.*' } | ForEach-Object {
#Foreach property with a Cust. prefix
$PropertyName = $_.Name
$PropertyValue = $_.Value
Write-Host "$PropertyName = '$PropertyValue'"
}
Output:
Cust.ID = '1'
Cust.Car = 'Mercedes'
If you are outputting an array and you only get a few properties (those available in the first object) like:
$Customer = #()
$Customer += [pscustomobject]#{ Name = "Object1"; "Cust.ID" = 1; "Cust.Car" = "Mercedes"}
$Customer += [pscustomobject]#{ Name = "Object2"; "Cust.ID" = 2; "Cust.Moped" = "Vespa"}
$Customer | Select-Object -Property "Cust.*"
Cust.ID Cust.Car
------- --------
1 Mercedes
2
Then you would want to create a full list of cust.*-properties and use that in your Select-Object-statement:
$AllCustProperties = $Customer | ForEach-Object { $_.psobject.Properties } | Where-Object { $_.Name -like 'Cust.*' } | Select-Object -ExpandProperty Name -Unique
$Customer | Select-Object -Property $AllCustProperties
Cust.ID Cust.Car Cust.Moped
------- -------- ----------
1 Mercedes
2 Vespa
You can inspect the property names of each custom object through the hidden psobject property:
$Customer[0].psobject.Properties |Where-Object {$_.Name -like 'Cust.*'} |Select Name,Value
You can also use this technique to enumerate all possible property names and feed those to Select-Object
$CustPropNames = $Customer |ForEach-Object {
$_.psobject.Properties |Where-Object {$_.Name -like 'Cust.*'} |Select-Object -Expand Name
} |Select-Object -Unique
$customerOptions = $Customer |Select-Object -Property $CustPropNames

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