Powershell to loop through .CSV to create Distribution List? - powershell

I want to modify the below PowerShell cmdlet to create bulk DLs with its members and its aliases.
Using the two cmdlets below:
https://learn.microsoft.com/en-us/powershell/module/exchange/new-distributiongroup?view=exchange-ps
https://learn.microsoft.com/en-us/powershell/module/exchange/set-distributiongroup?view=exchange-ps
I've managed to create just one DL when executed with the snippets below:
$paramNewDistributionGroup = #{
Name = $_.DisplayName
Alias = $_.Alias
PrimarySmtpAddress = $_.PrimarySmtpAddress
DisplayName = $_.DisplayName
RequireSenderAuthenticationEnabled = $False
Members = $_.Members
}
New-DistributionGroup #paramNewDistributionGroup
$paramSetDistributionGroup = #{
Identity = $_.Alias
EmailAddresses = #{ Add = $_.SecondarySMTPAddress }
}
Set-DistributionGroup #paramSetDistributionGroup
How to modify the above script so it takes the .CSV file which looks like the below:
Using the suggested code from #mklement below, it throws an error due to the multi-value entries on the below columns:
SecondarySMTPAddress column:
Write-ErrorMessage : Cannot process argument transformation on
parameter 'EmailAddresses'. Cannot convert value
"System.Collections.Generic.Dictionary`2[System.String,System.Object]"
to type "Microsoft.Exchange.Data.ProxyAddressCollection". Error: "The
address 'Execs#domain.com; boss#domain.com ' is invalid: The address
'Execs#domain.com; boss#domain.com' isn't a valid Unified Messaging
address, so a prefix must be specified."
Members column:
Write-ErrorMessage :
Ex94914C|Microsoft.Exchange.Configuration.Tasks.ManagementObjectNotFoundException|Couldn't
find object " Cella Cat; Maria Aya; Heni Amor; Dio O'meara". Please
make sure that it was spelled correctly or specify a different object.

Assuming this is on-prem Exchange?
$DLs = Import-Csv -Path <filepath>
$DLS | % {
$paramNewDistributionGroup = #{
Name = $_.DisplayName
Alias = $_.Alias
PrimarySmtpAddress = $_.PrimarySmtpAddress
DisplayName = $_.DisplayName
RequireSenderAuthenticationEnabled = $False
Members = $_.Members
}
New-DistributionGroup #paramNewDistributionGroup
$paramSetDistributionGroup = #{
Identity = $_.Alias
EmailAddresses = #{ Add = $_.SecondarySMTPAddress }
}
Set-DistributionGroup #paramSetDistributionGroup
}
I'm also going to assume the members within your CSV file match one of these AD attributes:
Name
Alias
Distinguished name (DN)
Canonical DN
Email address
GUID

Related

How to fix System Object value in PowerShell

I'm Importing a CSV file and reading a column that look like this
Exchange Mailboxes
Include:[john.doe#outlook.com]
Include:[david.smith#outlook.com]
Include:[kevin.love#outlook.com]
I use Get-EXOMailbox to get their DisplayName and Id. After that I'm trying to pass it in my New-Object like below so that I can export it. The problem I have is when I look at my Excel file, it showing System.Object[] on every row instead of showing each actual DisplayName and Id.
Any help on how to display it correctly would be really appreciated.
$result = Import-Csv "C:\AuditLogSearch\Dis\Modified-Audit-Log-Records.csv" |
Where-Object { -join $_.psobject.Properties.Value } |
ForEach-Object {
$exoMailbox = ($_.'Exchange Mailboxes' -split '[][]')[1]
$exoUser = Get-EXOMailbox -Filter "PrimarySmtpAddress -eq '$exoMailbox'"
# Construct and output a custom object with the properties of interest.
[pscustomobject] #{
UserName = $exoUser.DisplayName
UserId = $exoUser.Identity
}
}
New-Object PsObject -Property #{
'Searched User' = $result.UserName //I'm trying to pass here
'SharePoint URL' = $spUrl
'Searched User GMID' = $result.UserId //and here
'Site Owner' = $spositeOwner
User = $u.User
"Result Status" = $u."Result Status"
"Date & Time" = $u."Date & Time"
"Search Conditions" = $u."Search Conditions"
"SharePoint Sites" = $u."SharePoint Sites"
"Exchange Public Folders" = $u."Exchange Public Folders"
"Exchange Mailboxes" = $u."Exchange Mailboxes".Split([char[]]#('[', ']'))[1]
"Case Name" = $u."Case Name"
"Search Criteria" = $u."Search Criteria"
"Record Type" = $u."Record Type"
"Hold Name" = $u."Hold Name".Split(('\'))[1]
"Activity" = if ($null -ne ($importData | where-object { $_.Name -eq $u."Activity" }).Value) { ($importData | where-object { $_.Name -eq $u."Activity" }).Value }
else { $u."Activity" }
} | Select-object -Property User, "Date & Time", "Case Name", "Hold Name", "Record Type", "Activity" , "Searched User", "Searched User GMID", "SharePoint URL", "Exchange Mailboxes", "Exchange Public Folders" , "Search Criteria", "Result Status"
}
$xlsx = $result | Export-Excel #params
$ws = $xlsx.Workbook.Worksheets[$params.Worksheetname]
$ws.Dimension.Columns
$ws.Column(1).Width = 20
$ws.Column(2).Width = 20
$ws.Column(3).Width = 15
$ws.Column(4).Width = 15
$ws.Column(5).Width = 15
$ws.Column(6).Width = 160
$ws.View.ShowGridLines = $false
Close-ExcelPackage $xlsx
$result is an array of objects, containing an object for each non-empty row in your input CSV; thus, adding values such as $result.UserName to the properties of the object you're creating with New-Object will be arrays too, which explains your symptom (it seems that Export-Excel, like Export-Csv doesn't meaningfully support array-valued properties and simply uses their type name, System.Object[] during export).
It sounds like the easiest solution is to add the additional properties directly in the ForEach-Object call, to the individual objects being constructed and output via the existing [pscustomobject] literal ([pscustomobject] #{ ... }):
$result =
Import-Csv "C:\AuditLogSearch\Dis\Modified-Audit-Log-Records.csv" |
Where-Object { -join $_.psobject.Properties.Value } | # only non-empty rows
ForEach-Object {
$exoMailbox = ($_.'Exchange Mailboxes' -split '[][]')[1]
$exoUser = Get-EXOMailbox -Filter "PrimarySmtpAddress -eq '$exoMailbox'"
# Construct and output a custom object with the properties of interest.
[pscustomobject] #{
UserName = $exoUser.DisplayName
UserId = $exoUser.Identity
# === Add the additional properties here:
'Searched User' = $exoUser.UserName
'SharePoint URL' = $spUrl
'Searched User GMID' = $exoUser.UserId
'Site Owner' = $spositeOwner
# ...
}
}
Note:
The above shows only some of the properties from your question; add as needed (it is unclear where $u comes from in some of them.
Using a custom-object literal ([pscustomobject] #{ ... }) is not only easier and more efficient than a New-Object PSObject -Property #{ ... }[1] call, unlike the latter it implicitly preserves the definition order of the properties, so that there's no need for an additional Select-Object call that ensures the desired ordering of the properties.
[1] Perhaps surprisingly, PSObject ([psobject]) and PSCustomObject ([pscustomobject]) refer to the same type, namely System.Management.Automation.PSObject, despite the existence of a separate System.Management.Automation.PSCustomObject, which custom-objects instances self-report as (([pscustomobject] #{}).GetType().FullName) - see GitHub issue #4344 for background information.

Possible to pull info from AdditionalProperties dictionary with Microsoft Graph PowerShell cmdlets?

I am trying to use PowerShell Graph cmdlets instead of the Azure AD module cmdlets. With the Azure AD module, I can do this:
# This is what I want:
get-azureadgroupmember -objectid $GroupID | select-object -property displayname, `
mail, userprincipalname, objectid
DisplayName Mail UserPrincipalName ObjectId
----------- ---- ----------------- --------
John Smith John.Smith#example.org jsmith#example.org 4bae8291-6ec3-192b-32ce-dd21869ef784
(...)
# All of these properties are directly accessible in the returned objects:
$res = get-azureadgroupmember -objectid $GroupID
$res[0] | fl -prop *
# Shows long list of directly accessible properties
I'm trying to figure out the equivalent with PowerShell Graph:
$res = get-mggroupmember -groupid $GroupID
$res[0] | fl -prop *
# Only properties are DeletedDateTime, Id, and AdditionalProperties
# Want to do something like this, but it doesn't work:
get-mggroupmember -groupid $GroupID | select-object -property id, `
additionalproperties['displayName'], additionalproperties['mail'], `
additionalproperties['userPrincipalName']
# This works, but is there a better option???
get-mggroupmember -groupid $GroupID | foreach-object { `
"{0},{1},{2},{3}" -f $_.id, $_.additionalproperties['displayName'], `
$_.additionalproperties['mail'], $_.additionalproperties['userPrincipalName']
}
AdditionalProperties is a dictionary (IDictionary) which contains displayname, mail, and userprincipalname. My thought is there is probably a better way to do this or to get at the information.
There are a few interesting parameters in get-mggroupmember that I'm not clear on including "-expandproperty" and "-property". I've tried playing around with these but haven't had any luck. I'm wondering if there's a way to use these to do what I want.
Suggestions?
Given the following $object, 3 properties and one of them AdditionalProperties is a Dictionary<TKey,TValue>:
$dict = [Collections.Generic.Dictionary[object, object]]::new()
$dict.Add('displayName', 'placeholder')
$dict.Add('mail', 'placeholder')
$dict.Add('userPrincipalName', 'placeholder')
$object = [pscustomobject]#{
DeletedDateTime = 'placeholder'
Id = 'placeholder'
AdditionalProperties = $dict
}
Supposing from this object you're interested in Id, displayName and mail, you could use Select-Object with calculated properties:
$object | Select-Object #(
'Id'
#{
Name = 'displayName'
Expression = { $_.additionalProperties['displayName'] }
}
#{
Name = 'mail'
Expression = { $_.additionalProperties['mail'] }
}
)
However this gets messy as soon as you need to pick more property values from the objects, PSCustomObject with a loop comes in handy in this case:
$object | ForEach-Object {
[pscustomobject]#{
Id = $_.Id
displayName = $_.additionalProperties['displayName']
mail = $_.additionalProperties['mail']
}
}
Both alternatives would output the same "flattened" object that can be converted to Csv without any issue:
As Object
Id displayName mail
-- ----------- ----
placeholder placeholder placeholder
As Csv
"Id","displayName","mail"
"placeholder","placeholder","placeholder"
In that sense, you could construct an array of objects using one of the above techniques, for example:
Get-MgGroupMember -GroupId $GroupID | ForEach-Object {
[pscustomobject]#{
Id = $_.id
displayName = $_.additionalproperties['displayName']
mail = $_.additionalproperties['mail']
userPrincipalName = $_.additionalproperties['userPrincipalName']
}
}
If you're looking for a programmatical way to flatten the object, you can start by using this example, however it's important to note that this can only handle an object which's property is nested only once, in other words, it can't handle recursion:
$newObject = [ordered]#{}
foreach($property in $object.PSObject.Properties) {
if($property.Value -is [Collections.IDictionary]) {
foreach($addproperty in $property.Value.GetEnumerator()) {
$newObject[$addproperty.Key] = $addproperty.Value
}
continue
}
$newObject[$property.Name] = $property.Value
}
[pscustomobject] $newObject
The output from this would become a flattened object like this, which also, can be converted to Csv without any issue:
DeletedDateTime : placeholder
Id : placeholder
displayName : placeholder
mail : placeholder
userPrincipalName : placeholder
It's also worth noting that above example is not handling possible key collision, if there are 2 or more properties with the same name, one would override the others.
Bonus function that should work with the objects returned by the cmdlets from Graph, AzureAD and Az Modules. This function can be useful to flatten their Dictionary`2 property. It only looks one level deep if the property value implements IDictionary so don't expect it to flatten any object. For the given example should work well.
function Select-GraphObject {
[CmdletBinding()]
param(
[parameter(ValueFromPipeline, DontShow)]
[object] $InputObject,
[parameter(Position = 0)]
[string[]] $Properties = '*'
)
begin {
$firstObject = $true
$toSelect = [Collections.Generic.List[object]]::new()
}
process {
if($firstObject) {
foreach($property in $InputObject.PSObject.Properties) {
foreach($item in $Properties) {
if($property.Value -is [Collections.IDictionary]) {
foreach($key in $property.Value.PSBase.Keys) {
if($key -like $item -and $key -notin $toSelect.Name) {
$toSelect.Add(#{
$key = { $_.($property.Name)[$key] }
})
}
}
continue
}
if($property.Name -like $item -and $property.Name -notin $toSelect) {
$toSelect.Add($property.Name)
}
}
}
$firstObject = $false
}
$out = [ordered]#{}
foreach($item in $toSelect) {
if($item -isnot [hashtable]) {
$out[$item] = $InputObject.$item
continue
}
$enum = $item.GetEnumerator()
if($enum.MoveNext()) {
$out[$enum.Current.Key] = $InputObject | & $enum.Current.Value
}
}
[pscustomobject] $out
}
}
Using copies of the $object from above examples, if using the default value of -Properties, the example objects would be flattened:
PS /> $object, $object, $object | Select-GraphObject
DeletedDateTime Id displayName mail userPrincipalName
--------------- -- ----------- ---- -----------------
placeholder placeholder placeholder placeholder placeholder
placeholder placeholder placeholder placeholder placeholder
placeholder placeholder placeholder placeholder placeholder
Or we can filter for specific properties, even Keys from the AdditionalProperties Property:
PS /> $object, $object, $object | Select-GraphObject Id, disp*, user*
Id displayName userPrincipalName
-- ----------- -----------------
placeholder placeholder placeholder
placeholder placeholder placeholder
placeholder placeholder placeholder

Check and Update multiple attributes of AD users

I am trying to do an update to Active Directory from a CSV.
I want to check each value to see if the AD and CSV values match.
If the AD value and CSV values don't match, then I want to update the AD value.
finally I want to create a log of the values changed, which would eventually be exported to a CSV report.
Now there is about 30 values I want to check.
I could do an if statement for each value, but that seems like the hard way to do it.
I am try to use a function, but I cant seem to get it working.
I am getting errors like:
set-ADUser : replace
At line:94 char:9
+ set-ADUser -identity $ADUser -replace #{$ADValue = $DIAccount ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (JDoe:ADUser) [Set-ADUser], ADInvalidOperationException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.SetADUser
set-ADUser : The specified directory service attribute or value does not exist
Parameter name: Surname
At line:94 char:9
+ set-ADUser -identity $ADUser -replace #{$ADValue = $DIAccount ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (JDoe:ADUser) [Set-ADUser], ArgumentException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Commands.SetADUser
Any suggestions would be welcome
Code I am using:
Function AD-Check ($ADValue, $ADUser, $ADAccount, $UpdateAccount)
{
If ($ADAccount -ne $UpdateAccount)
{
set-ADUser -identity $ADUser -replace #{$ADValue = $UpdateAccount}
$Change = "Updated"
}
Else
{
$Change = "No Change"
}
Return $Change
}
$Import = get-content C:\temp\ADUpdates.csv
Foreach ($user in $Import)
{
$Account = get-aduser $User.Samaccountname -Properties *
#First Name Check
$Test = AD-Check "GivenName" $Account.samaccountname $Account.givenname $user.givenname
$ChangeGivenName = $Test
#Initials Check
$Test = AD-Check "Initials" $Account.samaccountname $Account.Initials $user.Initials
$ChangeInitials = $Test
#Last Name Check
$Test = AD-Check "Surname" $Account.samaccountname $Account.SurnameSurname $user.Surname
$ChangeSurname = $Test
}
Reply to Theo, cant seem to add this any other way...
Thanks Theo, it seems to make sense, but getting an error.
Select-Object : Cannot convert System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection to one of the following types {System.String,
System.Management.Automation.ScriptBlock}.
changed the following to get all properties for testing and it works.
$Account = Get-ADUser -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue -Properties $propsToCheck
Left the following and it kicks the error
$oldProperties = $Account | Select-Object $propsToCheck
Using the following just for testing:
$propertiesMap = [ordered]#{
SamAccountName = 'sAMAccountName'
mail = 'mail'
GivenName = 'givenName'
Initials = 'initials'
Surname = 'sn'
Office = 'physicalDeliveryOfficeName'
MobilePhone = 'mobile'
DistinguishedName = 'DistinguishedName'
}
Starting of with a WARNING:
Replacing user attributes is not something to be taken lightly and you
need to check any code that does that on a set of testusers first.
Keep the -WhatIf switch to the Set-ADUser cmdlet so you
can first run this without causing any problems to the AD.
Only once you are satisfied all goes according to plan, remove the -WhatIf switch.
Please carefully read all inline comments in the code.
In your code you use an input CSV file, apparently with properties and values to be checked/updated, but instead of using Import-Csv, you do a Get-Content on it, so you'll end up with just lines of text, not an array of parsed properties and values..
Next, as Mathias already commented, you need to use the LDAP attribute names when using either the -Add, -Remove, -Replace, or -Clear parameters of the Set-ADUser cmdlet.
To do what you intend to do, I would first create a hashtable to map the PowerShell attribute names to their LDAP equivalents.
To see which property name maps to what LDAP name, you can use the table here
# create a Hashtable to map the properties you want checked/updated
# the Keys are the PowerShell property names as they should appear in the CSV
# the Values are the LDAP AD attribute names in correct casing.
$propertiesMap = [ordered]#{
SamAccountName = 'sAMAccountName'
GivenName = 'givenName'
Initials = 'initials'
Surname = 'sn'
Office = 'physicalDeliveryOfficeName'
Organization = 'o'
MobilePhone = 'mobile'
# etcetera
}
# for convenience, store the properties in a string array
$propsToCheck = $propertiesMap.Keys | ForEach-Object { $_.ToString() }
# import your CSV file that has all the properties you need checked/updated
$Import = Import-Csv -Path 'C:\temp\ADUpdates.csv'
# loop through all items in the CSV and collect the outputted old and new values in variable $result
$result = foreach ($user in $Import) {
$sam = $user.SamAccountName
# try and find the user by its SamAccountName and retrieve the properties you really want (not ALL)
$Account = Get-ADUser -Filter "SamAccountName -eq '$sam'" -ErrorAction SilentlyContinue -Properties $propsToCheck
if (!$Account) {
Write-Warning "A user with SamAccountName '$sam' does not exist"
continue # skip this one and proceed with the next user from the CSV
}
# keep an object with the current account properties for later logging
$oldProperties = $Account | Select-Object $propsToCheck
# test all the properties and create a Hashtable for the ones that need changing
$replaceHash = #{}
foreach ($prop in $propsToCheck) {
if ($Account.$prop -ne $user.$prop) {
$ldapAttribute = $propertiesMap[$prop] # get the LDAP name from the $propertiesMap Hash
# If any of the properties have a null or empty value Set-ADUser will return an error.
if (![string]::IsNullOrWhiteSpace($($user.$prop))) {
$replaceHash[$ldapAttribute] = $user.$prop
}
else {
Write-Warning "Cannot use '-Replace' with empty value for property '$prop'"
}
}
}
if ($replaceHash.Count -eq 0) {
Write-Host "User '$sam' does not need updating"
continue # skip this one and proceed with the next user from the CSV
}
# try and do the replacements
try {
##########################################################################################################
# for safety, I have added a `-WhatIf` switch, so this wll only show what would happen if the cmdlet runs.
# No real action is performed when using '-WhatIf'
# Obviously, there won't be any difference between the 'OLD_' and 'NEW_' values then
##########################################################################################################
$Account | Set-ADUser -Replace $replaceHash -WhatIf
# refresh the account data
$Account = Get-ADUser -Identity $Account.DistinguishedName -Properties $propsToCheck
$newProperties = $Account | Select-Object $propsToCheck
# create a Hashtable with the old and new values for log output
$changes = [ordered]#{}
foreach ($prop in $propsToCheck) {
$changes["OLD_$property"] = $oldProperties.$prop
$changes["NEW_$property"] = $newProperties.$prop
}
# output this as object to be collected in variable $result
[PsCustomObject]$changes
}
catch {
Write-Warning "Error changing properties on user '$sam':`r`n$($_.Exception.Message)"
}
}
# save the result as CSV file so you can open with Excel
$result | Export-Csv -Path 'C:\temp\ADUpdates_Result.csv' -UseCulture -NoTypeInformation

Powershell CSV ordered Export with specific columns

i have following problem:
I have a query with Get-ADUser and have there specific fields to get (only test query)
$AllADUsers = get-aduser -Identity m.mustermann -Properties * | select mail, title, l, GivenName, Surname, company, department, mobile
Also i must add some static fields how language or comment in to the CSV, i take some variable for that:
$Language = "german"
$Comment = ""
$Link = ""
$gender = ""
$exportto = "C:\Temp\Export01.csv"
Than i want to export all entries in a ordered UTF8 CSV with Delimiter ":".
I do a foreach but always i get mistakes or not complete lists - i show you my code (but i have mistakes)
Foreach ($ADUser in $AllADUsers) {
$MailAddress = $ADUser.mail
$FullName = "$ADUser.GivenName" + "$ADUser.Surname"
$Title = $AdUser.Title
$Location = $ADUser.l
$Comment = $Comment
$Link = $Link
$MobilePhone = $ADUser.mobile
$Language = $Language
$Divison = $ADUser.Department
$GivenName = $ADUser.GivenName
$Surname = $ADUser.Surname
}
$ADUser | Export-CSV -Path $exportto -NoTypeInformation -Delimiter ":" -Encoding UTF8
Can you help me?
The export must be with the ordered list and without "" and just in this order.
Thank you.
Max
Your code doesn't work, mainly because you are creating a series of variables that in the end you do not use.
To output to CSV, you need to collect objects with properties, not single variables.
A very elegant way is to create those objects using [PsCustomObject] syntax, where at the same time the order in which you set the properties make up for the order in the final output file.
Using the colon as delimiter character is unusual and may get you into trouble when other applications need to read and understand this, but that is up to you.
Another thing is that you mainly seem to use LDAP attribute names, where PowerShell has most of them mapped to more friendly attribute names (like 'City' which maps to LDAP 'l').
Your code revised:
# Get-ADUSer already returns these properties by default:
# DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname, UserPrincipalName
# your hardcoded static variables:
$Language = "german"
$Comment = ""
$Link = ""
$gender = ""
$exportto = "C:\Temp\Export01.csv"
# these are the extra properties you want to fetch
$userprops = 'EmailAddress','Title','Company','City','Department','MobilePhone'
# if you want this for all users, remove "-Identity 'm.mustermann'" and use "-Filter *" instead
$result = Get-ADUser -Identity 'm.mustermann' -Properties $userprops | ForEach-Object {
# output an object to be collected in variable $result
# the order in which you put them also determines the field order in the CSV
[PsCustomObject]#{
MailAddress = $_.EmailAddress
FullName = $_.DisplayName
Title = $_.Title
Company = $_.Company
Location = $_.City
Comment = $Comment # static field
Link = $Link # static field
MobilePhone = $_.MobilePhone
Language = $Language # static field
Department = $_.Department
GivenName = $_.GivenName
Surname = $_.Surname
}
}
$result | Export-CSV -Path $exportto -NoTypeInformation -Delimiter ":" -Encoding UTF8
A note about Export-Csv:
Export-Csv will always quote everything, be it a header or data field.
Simply trying to remove all quotes in a CSV file is risking mis-alignment in the field order and rendering the csv as unusable.
With PowerShell version 7 you have the option to use parameter -UseQuotes AsNeeded, but for older versions, you may safely do this using my function ConvertTo-CsvNoQuotes
Since this code is all about getting AD information, I don't see why you would want to construct the FullName here. Just use the DisplayName property that is set in the AD as the code above does
There are some high-level issues in your code.
Incorrect assignment
In your code you should assign the values to specific properties of $AdUser. Something like:
foreach ($ADUser in $AllADUsers) {
$ADUser.FullName = "$($ADUser.GivenName) $($ADUser.Surname)"
$ADUser.Comment = $Comment
$ADUser.Link = $Link
}
No export
Currently you export only last entry, you should pipe $AllADUsers to Export-Csv:
$AllADUsers| Export-CSV -Path $exportto -NoTypeInformation -Delimiter ":" -Encoding UTF8
No use of Select-Object calculated properties
In general, you could have your code shorten to one-liner like:
$AllADUsers | Select-Object #{n="Comment";{$Comment}},#{n="FullName";e={"$($ADUser.GivenName) $($ADUser.Surname)"}} ... | Export-CSV ...
Read more about calculated properties here.

Trying to strip out unwanted text in PowerShell

I'm making a PowerShell script which queries our Office 365 tenants and exports certain information into a .csv file. The two fields I'm struggling with are the users default email address and their assigned subscriptions. I can get the data, but not sure how to manipulate it and make it look more presentable.
Get-MSOLUser -All | select firstname,lastname,displayname,islicensed,{$_.Licenses.AccountSkuId},{$_.proxyaddresses -cmatch '^SMTP\:.*'},userprincipalname | sort FirstName | Export-Csv $directory\$tenantname\Export.csv -NoTypeInformation
1) I've managed to get their primary email address as lower cased smtp addresses will always be aliases, but how do I strip out the "SMTP:" part?
2) Instead of "reseller-account:SKU Part Number" I was hoping to shorten this to the names we usually refer them as! Such as:
"E3" instead of "reseller-account:ENTERPRISEPACK"
"E5" instead of "reseller-account:ENTERPRISEPREMIUM"
"ProjectPro" instead of "reseller-account:PROJECTPROFESSIONAL"
"Visio" instead of "reseller-account:VISIOCLIENT"
Two questions really but very similar! Hope you can help.
To Achieve that you can use Calculated Properties along with a small function to convert the SkuId's to a Friendly name and using -replace to remove the SMTP part , I've Created for you a simple function for the conversion, you can add other products just like i did:
The Microsoft Product Name/SKU's list can be found in this link
function Convert-SkuIdToFriendlyName
{
Param(
[string]$SkuId
)
switch ($SkuId)
{
{$SkuId -match "ENTERPRISEPACK"} {return "OFFICE 365 ENTERPRISE E3"}
{$SkuId -match "ENTERPRISEPREMIUM"} {return "OFFICE 365 ENTERPRISE E5"}
default { 'Unknown' }
}
}
Then use a Calculated properties to replace the 'SMTP' part and convert the SkuId:
Get-MSOLUser -All |
Select firstname,lastname,displayname,islicensed,
#{N="License";E={Convert-SkuIdToFriendlyName $_.Licenses.AccountSkuId}},
#{N="Email";E={$_.proxyaddresses -cmatch '^SMTP\:.*' -replace 'SMTP\:'}},userprincipalname |
Sort FirstName
You can use a hashtable as a lookup table for the wanted translations like so:
# create a hash with the desired translations.
# below are just the examples from your question. You need to fill in the rest..
$Licenses = #{
"ENTERPRISEPACK" = "E3"
"ENTERPRISEPREMIUM" = "E5"
"PROJECTPROFESSIONAL" = "ProjectPro"
"VISIOCLIENT" = "Visio"
}
Get-MSOLUser -All |
Select-Object firstname,lastname,displayname,islicensed,userprincipalname,
#{ Name = 'License'; Expression = { $Licenses[$(($_.Licenses.AccountSkuId) -replace '^.+:', '')] }},
#{ Name = 'PrimaryEmailAddress'; Expression = { ($_.proxyaddresses -cmatch '^SMTP\:.*') -replace "SMTP:", "" }} |
Sort-Object FirstName | Export-Csv $directory\$tenantname\Export.csv -NoTypeInformation
In order to get all licenses a user can have listed, the code could be extended to:
# create a hash with the desired translations for the license plans.
# below are just the examples from your question. You need to fill in the rest..
$Licenses = #{
"ENTERPRISEPACK" = "E3"
"ENTERPRISEPREMIUM" = "E5"
"PROJECTPROFESSIONAL" = "ProjectPro"
"VISIOCLIENT" = "Visio"
}
# this calculated property returns all (translated) licenses per user as comma delimited string
$userLicenses = #{
Name = 'Licenses'
Expression = {
$result = #()
foreach($lic in $_.Licenses) {
$result += $Licenses[$(($lic.AccountSkuId) -replace '^.+:', '')]
}
$result -join ', '
}
}
Get-MSOLUser -All |
Select-Object firstname,lastname,displayname,islicensed,userprincipalname,$userLicenses,
#{ Name = 'PrimaryEmailAddress'; Expression = { ($_.proxyaddresses -cmatch '^SMTP\:.*') -replace "SMTP:", "" }} |
Sort-Object FirstName | Export-Csv $directory\$tenantname\Export.csv -NoTypeInformation