Trouble parsing string to object with PowerShell - string

I have a string with structured data (see below). I need to take this string and convert it to an object, so I can export it to .csv (or whatever else is requested of me). I ran the following code:
$data = $string -replace "\s*:\s*","="
But my output looks like this:
City=Country=Department=DisplayName=John Doe
DistinguishedName=CN=John Doe, CN=Users, DC=domain, DC=com
EmailAddress=jdoe#domain.com
Enabled=False
Fax=GivenName=John
MobilePhone=Name=John Doe
ObjectClass=user
ObjectGUID=cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office=OfficePhone=PostalCode=SamAccountName=jdoe
SID=S-1-5-21-2025429266-2000478354-1606980848-16934
State=StreetAddress=Surname=Doe
Title=UserPrincipalName=jdoe#domain.com
This is clearly not correct. What is a better way to make this conversion? I thought about using ConvertFrom-String with the TemplateContent parameter, but haven't been able to make that work yet.
Here are the first two entries in the string (which contains several users worth of data):
$string = #"
City :
Country :
Department :
DisplayName : John Doe
DistinguishedName : CN=John Doe,CN=Users,DC=domain,DC=com
EmailAddress : jdoe#domain.com
Enabled : False
Fax :
GivenName : John
MobilePhone :
Name : John Doe
ObjectClass : user
ObjectGUID : cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office :
OfficePhone :
PostalCode :
SamAccountName : jdoe
SID : S-1-5-21-2025429266-2000478354-1606980848-16934
State :
StreetAddress :
Surname : Doe
Title :
UserPrincipalName : jdoe#domain.com
City :
Country :
Department :
DisplayName : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}
DistinguishedName : CN=DiscoverySearchMailbox {D919BA15-46A6-415f-80AD-7E09334BB852},CN=Users,DC=domain,DC=com
EmailAddress : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}#domain.com
Enabled : False
Fax :
GivenName :
MobilePhone :
Name : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}
ObjectClass : user
ObjectGUID : 0f35137a-de93-472f-9114-5488a462d178
Office :
OfficePhone :
PostalCode :
SamAccountName : SM_2187102a90634829b
SID : S-1-5-21-2438891277-1009865731-3229889747-3109
State :
StreetAddress :
Surname : MsExchDiscoveryMailbox D919BA15-46A6-415f-80AD-7E09334BB852
Title :
UserPrincipalName : DiscoverySearchMailbox{D919BA15-46A6-415f-80AD-7E09334BB852}#domain.com
"#
Thanks.

If:
you can rely on values never containing : themselves
you don't mind that the properties of the resulting custom objects don't reflect the input order (though you could easily, but inefficiently, correct that with piping to a Select-Object call enumerating the properties explicitly),
you can use ConvertFrom-StringData (I suggest avoiding the finicky and poorly documented ConvertFrom-String):
$string.Trim() -split '(?m)(?=^City\b)' -ne '' | ForEach-Object {
[pscustomobject] ($_ -replace ':', '=' | ConvertFrom-StringData)
} # | Export-Csv ....
Note: Casting to [pscustomobject] requires PSv3+; on PSv2, use New-Object PSCustomObject -Property (...)
$string.Trim() -split '(?m)(?=^City\b)' -ne '' splits the input lines into blocks of lines each representing one object; splitting is performed by lines that start with City; -ne '' filters out the empty block that results from parsing the start of the input.
.Trim() is needed to ignore empty lines at the start of the string.
$_ -replace ':', '=' | ConvertFrom-StringData converts each block into
<key>=<value> lines that ConvertFrom-StringData converts as a group to a [hashtable] instance; because hash tables inherently enumerate their entries in no guaranteed order, this is where the input ordering of properties is lost.
Cast [pscustomobject] converts each hashtable to a custom object, which is implicitly output; the output can be piped to Export-Csv.

Here You go:)
$a=#"
City :
Country :
Department :
DisplayName : John Doe
DistinguishedName : CN=John Doe,CN=Users,DC=domain,DC=com
EmailAddress : jdoe#domain.com
Enabled : False
Fax :
GivenName : John
MobilePhone :
Name : John Doe
ObjectClass : user
ObjectGUID : cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office :
OfficePhone :
PostalCode :
SamAccountName : jdoe
SID : S-1-5-21-2025429266-2000478354-1606980848-16934
State :
StreetAddress :
Surname : Doe
Title :
UserPrincipalName : jdoe#domain.com
"#
$b=ConvertFrom-Csv -InputObject $a -Delimiter ':' -Header "key","value"
$c=New-Object -TypeName System.Management.Automation.PSObject
$b|%{ $c|Add-Member -NotePropertyName $_.key -NotePropertyValue "$($_.value)"}
Resulting object looks like this
PS C:\Users\Tomasz> $c|gm
S C:\Users\Tomasz> $c
City :
Country :
Department :
DisplayName : John Doe
DistinguishedName : CN=John Doe,CN=Users,DC=domain,DC=com
EmailAddress : jdoe#domain.com
Enabled : False
Fax :
GivenName : John
MobilePhone :
Name : John Doe
ObjectClass : user
ObjectGUID : cdb9a45c-80f4-4919-bf43-5db8d9ca83da
Office :
OfficePhone :
PostalCode :
SamAccountName : jdoe
SID : S-1-5-21-2025429266-2000478354-1606980848-16934
State :
StreetAddress :
Surname : Doe
Title :
UserPrincipalName : jdoe#domain.com
If this kind of solution seems like a good Idea I'll work on my answer more.
It obviously needs white spaces removal and some nicer variable names, but I trust You can get that done Yourself :)

The escape sequence \s matches all whitespace, including newlines. Because of that lines without a value are actually merged with the next line. Split the string at newlines, do the replacement, then merge the string array back to a single string.
$data = $string -split '\r?\n' -replace '\s*:\s*','=' | Out-String
or make sure you don't replace line break characters:
$data = $string -replace '[\t ]*:[\t ]*', '='
Edit:
Since your input data seems to consist of multiple records, not just one, you need to split the resulting string by record, so that you have individual strings per data set. Convert each data set to a hashtable with ConvertFrom-StringData, then convert those hashtables to custom objects.
$data = $string -split '(?<=\r?\n)\r?\n' | ForEach-Object {
$prop = $_.Trim() -split '\r?\n' -replace '\s*:\s*','=' |
Out-String |
ConvertFrom-StringData
New-Object -Type PSObject -Property $prop
}
In PowerShell v3 and newer you can use the [PSCustomObject] type accelerator instead of New-Object:
$data = $string -split '(?<=\r?\n)\r?\n' | ForEach-Object {
$prop = $_.Trim() -split '\r?\n' -replace '\s*:\s*','=' |
Out-String |
ConvertFrom-StringData
[PSCustomObject]$prop
}
The resulting list of objects can then be exported to a CSV.

Related

Change output results in PowerShell

I want to get all user ID's with a specific token assigned.
It looks like this now when I run my script..
Get-ADUser -Filter * -Properties * | Select-Object vasco-LinkUserToDPToken, displayname
#Output#
vasco-LinkUserToDPToken Displayname
{CN=VES0423061,OU=br... User X
{} User X
{} User X
{CN=0067511310,OU=br... User X
{CN=0067077717,OU=br... User X
Example of a full vasco-LinkUserToDPToken :
{CN=VES0976944,OU=Internal Users,DC=mgm,DC=agf,DC=be}
the thing is I only want to filter VES + it should be shown like this (not containing empty strings or tokens that are not starting with VES):
VES0423061 User X
It looks like your property 'vasco-LinkUserToDPToken' is a multivalued property type (string array) of which you need to extract the DN inside.
You could try:
Get-ADUser -Filter "vasco-LinkUserToDPToken -like 'CN=VES*'" -Properties 'vasco-LinkUserToDPToken', DisplayName |
Select-Object #{Name = 'vasco-LinkUserToDPToken'; Expression = {
($_.'vasco-LinkUserToDPToken' | Where-Object {$_ -match '^CN=VES.*'}) -replace '.*(VES[^,]+).*', '$1'}
}, DisplayName
P.S. It is always a bad idea to use -Properties * is what you are after is just two properties. Using * forces to pull down ALL properties which is a waste of time
If the -Filter doesn't work on this custom property, you can always use a Where-Object clause afterwards like:
Get-ADUser -Filter * -Properties 'vasco-LinkUserToDPToken', DisplayName |
Where-Object { $_.'vasco-LinkUserToDPToken' -like 'CN=VES*' } |
Select-Object #{Name = 'vasco-LinkUserToDPToken'; Expression = {
($_.'vasco-LinkUserToDPToken' | Where-Object {$_ -match '^CN=VES.*'}) -replace '.*(VES[^,]+).*', '$1'}
}, DisplayName

View groups for every user in Azure AD with powershell

As the title said. im looking for a way to list every user, with the group(s), they are in.
I'm aware of how you could use Get-AzureADGroupMember -ObjectId "groupidhere"
and then the output is all the users in that group. but how would you automate this? is this even possible to do with powershell?
after this ill be using this table to create a table in Hudu. i havent seen anyone do this with groups and users together though, so for all i know its not possible or supposed to be.
So the output i get here from $Users to also show some of the output from $Groups_Name
A table where i have all the info about a user, but also what groups they are in.
| Name | Email | Group |
so the output would be something like this:
DisplayName UserPrincipalName DisplayName
----------- ----------------- -----------
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Name Nameson user#domain.com Group names
Script im working on (i know this is super messy)
# Table of all users
$Users = Get-AzureADUser -All:$true
# Table of all groups
$Groups = Get-AzureADGroup
# ALL users ObjectId
$Users_ObjectId = $Users | Select-Object ObjectId
# ALL Groups ObjectId
$Groups_ObjectId = $Groups | Select-Object ObjectId
#Group names - list
$Groups_Name = $Groups | Select-Object DisplayName
#User names - list
$Users_Name = $Users | Select-Object DisplayName
foreach ($i in $Users ) {
# If
if ($Groups -contains $Users_ObjectId) {
#print a table with desired formatting
#$Users $Groups_Name
}
}
Try using Get-AzureADUserMembership like this:
$users = Get-AzureADUser -All $true
$report = Foreach ($user in $users) {
$groups = $user | Get-AzureADUserMembership
# create output objects with username and groups:
Foreach ($group in $groups) {
[PSCustomObject][ordered]#{
UserDisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
GroupDisplayName = $group.DisplayName
}}}
# print a table with desired formatting
$report | ft
And the report looks like so:
UserDisplayName UserPrincipalName GroupDisplayName
--------------- ----------------- ----------------
John Smith j.smith#domain.com Marketing
John Smith j.smith#domain.com Marketing-VIPs
John Doe j.doe#domain.com Sales
John Doe j.doe#domain.com Management

Converting email string into .csv columns

I have a giant .txt file full of emails in this format:
Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;
I wanted to know if there is a way to convert this .txt file into a .csv that will have two columns: one for name and one for email:
Column 1:
Doe, John L (Male)
Smith, Jane M (Female)
Column 2:
JohnD#email.com
JaneS#email.com
I am a bit overwhelmed as to how to start this, I know the commas in the names make this a bit complicated. Any help is appreciated
Here's an example of parsing your file to psobject with regex, then just export to CSV as usual.
$In = 'Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;' #use get-content here
$SplitInput = $In -split ';' | Where-Object {$_ -ne ''} #filter in case of extra ; at start or end as in example
$Users = $SplitInput | ForEach-Object {
$_ -match '^\s*(?<Name>.*) <(?<Email>.*)>$' | Out-Null
New-Object PSCustomObject #{
Name = $Matches.Name
Email = $Matches.Email
}
}
Details of regex used- https://regexr.com/5rfjq
There is no space in emails so something like this
$content = "Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;"
$content.Split(";") | % { $_.Trim() } | ? { $_ } | % {
$iSpace = $_.LastIndexOf(" "); [PSCustomObject]#{ Name = $_.Substring(0, $iSpace) ; Email = $_.Substring($iSpace+2).TrimEnd(">")
} } | ConvertTo-Csv
Result :
"Name","Email"
"Doe, John L (Male)","JohnD#email.com"
"Smith, Jane M (Female)","JaneS#email.com"
Regex is probably the easiest way to do it here. The trouble is that formats can vary quite a bit.
$EmailAddresses = 'Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;'
$EmailAddresses -split ';' |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
ForEach-Object {
if ($_ -match '\s*(?<Name>.*?)\s*<(?<Email>.*)>\s*') {
[PSCustomObject]#{Name = $Matches['Name']; Email = $Matches['Email'] }
}
else {
Write-Warning "Unrecognized name and email in '$_'"
}
} |
Export-Csv $ExportFile -NoTypeInformation
This should parse your content and create a CSV file with the two columns. It will raise a warning on any entry that it doesn't understand, although it will ignore any entry that is whitespace only.
Here's an explanation of the regex:
'\s*(?<Name>.*?)\s*<(?<Email>.*)>\s*'
\s*: Zero or more whitespace characters
(?<Name>.*): Named capture group 'Name' which has some amount of any characters
\s*: Zero or more whitespace characters
<: The literal character <
(?<Email>.*): Named capture group 'Email' which has some amount of any characters
>: The literal character >
\s*: Zero or more whitespace characters
Another option would be to split the strings and convert each one to a System.Net.Mail.MailAddress which has a properties for Address and DisplayName. That may work the best, but the last time I tried it I ran into trouble. Unfortunately, I don't remember what the trouble was. I think it was commas in display names somehow being improper.
Ran the following provided by #Bacon Bits:
$content = Get-content -Path 'C:\.....txt'
$content -split ';' |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
ForEach-Object {
if ($_ -match '\s*(?<Name>.*?)\s*<(?<Email>.*)>\s*') {
[PSCustomObject]#{Name = $Matches['Name']; Email = $Matches['Email'] }
}
else {
Write-Warning "Unrecognized name and email in '$_'"
}
} | Export-Csv -Path 'C:\.....csv' -NoTypeInformation
You could do the following:
# Get file contents as string
$fileContents = Get-Content -Path .\sample.txt -Raw
# Split on ; to get each user
# Remove empty entries and trim also
$users = $fileContents.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries).Trim()
# Export each user to CSV file inside this scriptblock
& {
foreach ($user in $users) {
# Get index of last space
$splitIndex = $user.LastIndexOf(' ')
# Create PSCustomObject with Name and Email
# We can substring this with above split index
[PSCustomObject]#{
Name = $user.Substring(0, $splitIndex).Trim()
Email = $user.Substring($splitIndex + 1).Trim('<', '>')
}
}
} | Export-Csv -Path .\sample.csv -NoTypeInformation
Output
"Name","Email"
"Doe, John L (Male)","JohnD#email.com"
"Smith, Jane M (Female)","JaneS#email.com"
A lot of great answers. I'd like to add another option
$content = "Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;"
switch -Regex ($content -split ';'){
'\s?(.+)\s<(.+)>' {
[PSCustomObject]#{
Name = $Matches.1
Email = $Matches.2
}
}
}
output
Name Email
---- -----
Doe, John L (Male) JohnD#email.com
Smith, Jane M (Female) JaneS#email.com
To export csv just capture the output to a variable or you can surround it with a subexpression and then pipe
$content = "Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;"
$output = switch -Regex ($content -split ';'){
'\s?(.+)\s<(.+)>' {
[PSCustomObject]#{
Name = $Matches.1
Email = $Matches.2
}
}
}
$output | Export-Csv $outputfile -NoTypeInformation
or
$content = "Doe, John L (Male) <JohnD#email.com>; Smith, Jane M (Female) <JaneS#email.com>;"
$(switch -Regex ($content -split ';'){
'\s?(.+)\s<(.+)>' {
[PSCustomObject]#{
Name = $Matches.1
Email = $Matches.2
}
}
}) | Export-Csv $outputfile -NoTypeInformation

Powershell odd behaviour when outputting to csv

I'm having a problem when outputting my foreach loop to a csv file.
My Groups are set like this:
$Groups = "Group1", "Group2", "Group3"
My code is:
$results = ForEach ($Group in $Groups) {
$memberof = get-adgroup $Group | select -expandproperty distinguishedname
Write-Output $Group
Get-ADObject -Filter 'memberof -eq $memberof -and (ObjectClass -eq "user" -or ObjectClass -eq "contact")' -properties * | select name, Objectclass, mail
Write-Output ""
Write-Output ""
}
$results | Export-csv Contacts.csv -NoTypeInformation
The problem seems to be coming from the Write-Output lines but I have no clue why. When I run my code without writing to a csv file, I get the expected result, something like:
NameOfGroup1
name Objectclass mail
---- ----------- ----
User1 user User1#mail.com
User2 user User2#mail.com
#Spaces caused by write-output ""
NameOfGroup2
User1 user User1#mail.com
Contact1 contact Contact1#externalmail.com
Then again when I run my code to write to csv file and have the write-output $Group commented out I get a similar result.
But if I run my full code from the top of this page including the write-output $Group, it comes out like this:
I've figured out what these results represent but I haven't got clue why they do print out like this.
Eseentially the numbers refer to the length of the group name, so the first 17 would be a 17 character group name, and then the number of lines below is equal to the number of contacts and users that are inside that group. The 2 zeros at the end of each group are the length of the write-output "" lines.
What is causing this behavior?
The following code will closely output what you are attempting.
$results = ForEach ($Group in $Groups) {
$memberof = get-adgroup $Group | select -expandproperty distinguishedname
Get-ADUser -Filter "memberof -eq '$memberof' -and (ObjectClass -eq 'user' -or ObjectClass -eq 'contact')" -properties name,ObjectClass,Mail | Select-Object #{n='Group';e={$Group}},name, Objectclass, mail
[pscustomobject]"" | Select-Object Group,Name,ObjectClass,Mail
[pscustomobject]"" | Select-Object Group,Name,ObjectClass,Mail
}
$results | Export-csv Contacts.csv -NoTypeInformation
Explanation:
Export-Csv converts an object or array of objects with properties into a CSV file. You can see the same result in the console with ConvertTo-Csv. Properties are converted into columns and property values are placed under their associated columns. When you output a string as in Write-Output $Group, it has a property of Length. To fix this, you need to add $Group as a calculated property in your Select-Object. If you want to do blank lines in your CSV, then you should output another object with all of the property values as ''.
When you mix objects in your PowerShell outputs, you can see unexpected results. Your Get-ADObject outputs a custom object. Your Write-Output lines output a string. Those two object types do not share properties. So you only see the properties for the first object in your array, which is a string. If you put all of the Write-Output statements at the end of your loop, you will see more properties in your CSV. See below for an example that just by reversing the order of processed objects, you get a different result.
$str = "string"
$obj = [pscustomobject]#{property1 = "value1"; property2 = "value2"}
$str,$obj | convertto-csv -notype
"Length"
"6"
$obj,$str | convertto-csv -notype
"property1","property2"
"value1","value2"
,
Notice the properties available to the custom object $obj and the string $str.
$obj | get-member -Type Properties
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
property1 NoteProperty string property1=value1
property2 NoteProperty string property2=value2
$str | get-member -Type Properties
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}

string formatting with named index and a hashtable in PowerShell

I know how to use string formatting like this:
"{0,-20} | {1,5} | {2}" -f "bob", "3", "you#me.com"
But this gets very messy and confusing to read. Is there a way to use named indexes and a hashtable?
Something like this:
"{name,-20} | {age,5} | {mail}" ??? #{
"name" = "bob";
"age" = "3";
"mail" = "you#me.com"
}
You could approach it like this:
#{
"name" = "bob";
"age" = "3";
"mail" = "you#me.com"
} | ForEach-Object { "{0,-20} | {1,5} | {2}" -f $_.name,$_.age,$_.mail}
If you're interested in simplification and readability, I'd recommend breaking it down:
$info = #{
"name" = "bob";
"age" = "3";
"mail" = "you#me.com"
}
$fString = "{0,-20} | {1,5} | {2}"
$fString -f $info.Name, $info.age, $info.mail
$info = [pscustomobject]#{
"name" = "bob";
"age" = "3";
"mail" = "you#me.com"
}
$info # prints with headers
$info | Format-Table -HideTableHeaders # prints without headers
I try to avoid this kind of string formatting in PowerShell, since using variable expansion is usually much more readable. Even with your kinda rare formatting requirements, it still reads ok-ish. You can easily see which variable goes where, which is not the case when using the -f formatting.
$name = 'bob'
$age = 3
$mail = 'you#me.com'
"$($name.PadRight(20)) | $($age.ToString().PadLeft(5)) | $mail"

Resources