CSV export is missing data in one of the columns - powershell

I have a csv file that this script works fine on.
$CCure = Import-csv C:\Scripts\file1.csv
ForEach ($user in $CCure) {
[string]$1Name = $User.FirstName
[string]$2Name = $User.LastName
[string]$GivenName = $1Name.Split(" ")[0]
[string]$SN = $2Name.Split(",")[0]
[string]$ID = $User.ObjectID
[string]$EmpID = $User.Int5 |
Select #{Name="First Name";Expression={$GivenName}}, #{Name="Last Name";Expression={$SN}}, #{Name="CCure ObjectID";Expression={$ID}}, #{Name="CCure Int5 Row";Expression={$EmpID}} |
Export-csv C:\Scripts\CCure\CCure-Names-Test.csv -Append -notypeinformation
}
However, when I try a similar script going out to AD and want to use RegEx, things don't work. It seems to hang or when it does run, the SurName is missing. Why doesn't the second one work? I must be missing something simple?
$Users = Get-ADUser -LDAPFilter "(&(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))" -properties samaccountname,givenname,sn
ForEach ($User in $Users) {
[string]$1Name = $User.GivenName
[string]$2Name = $User.SN
[string]$GivenName = $1Name.Split(" ")[0]
[string]$SN = $2Name.Split(",")[0] |
Select #{Name="First Name";Expression={$GivenName}}, #{Name="Last Name";Expression={$SN}},#{Name="SID";Expression={$User.samaccountname}}| Export-Csv C:\scripts\ccure\AD-Active-Names3.csv -NoTypeInformation -append
}

In your second example $sn would be null. This is because of the trailing pipe character that you have. This issue is also present in your first code block for $EmpID. The last command Export-CSV returns nothing to the output stream so the variable in both cases would be $null.
You are taking the result of $2Name.Split(",")[0] and sending that into the pipeline which is then left unused for Select-Object.
So the simple answer is remove the pipeline character and those two lines will now work separately.
[string]$SN = $2Name.Split(",")[0]
Select #{Name="First Name";Expression={$GivenName}}, #{Name="Last Name";Expression={$SN}},#{Name="SID";Expression={$User.samaccountname}}| Export-Csv C:\scripts\ccure\AD-Active-Names3.csv -NoTypeInformation -append
Also consider making your own object if you are changing property names and values that much. This should accomplish the same thing, be easier to read and take advantage of the pipeline.
$Users = Get-ADUser -LDAPFilter "(&(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
$Users | ForEach-Object {
$props = #{
"First Name" = ($_.GivenName).Split(" ")[0]
"Last Name" = ($_.SurName).Split(",")[0]
"SID" = $_.samaccountname
}
New-Object -TypeName psobject -Property $props
} | Select-Object "First Name","Last Name",SID | Export-Csv C:\scripts\ccure\AD-Active-Names3.csv -NoTypeInformation
You will note that I used $_.SurName as the property names used since PowerShell AD Objects do not directly match their LDAP attribute names. If you tried to access $_.SN you would get a null value. GivenName,Surname and SID are part of the value set returned by default so you don't need to request them.
Looking at about_ActiveDirectory_ObjectModel you will see under AdUser:
Surname - A property of type System.String, derived from the directory attribute: sn
You will still get errors with this if Surname or GivenName are not populated. Accounting for that would be a simple if statement if that is going to be an issue.

Related

Working with a list of AD 'displayNames' in Powershell. How to indicate which users were not found?

I have written enough PS code to go through a list of displayNames (e.g "John Smith", "Taylor Hanson" - all stored on seperate lines of a txt file) to spit back enough data into another text file that can be used for mailmerge etc. Convincing thousands of employees to simply update Windows is like breaking stones! It has to be automatted to some degree...
Here is the code... the functions that let the user open a specific text file and later save are out of view...
$displayname = #()
$names = get-content $FileIN
foreach ($name in $names) {
$displaynamedetails = Get-ADUser -filter { DisplayName -eq $name } | Select Name, GivenName, Surname, UserPrincipalName
$displayname += $displaynamedetails
}
$displayname | Export-Csv -NoTypeInformation -path $fileOUT -Encoding UTF8
From time to time, a name might be spelled incorrectly in the list, or the employee may have left the organisation.
Is there any way that a statement such as 'Not Found' can be written to the specific line of the text file if an error is ever made (so that an easy side-by-side comparison of the two files can be made?
For most of the other solutions I've tried to find, the answers are based around the samAccoutName or merging the first and last names together. Here, i am specifically interested in displaynames.
Thanks
You can give this a try, since -Filter or -LDAPFilter don't throw any exception whenever an object couldn't be found (unless you're feeding a null value) you can add an if condition to check if the variable where the AD User object is going to be stored is not null and if it is you can add this "not found" user into a different array.
$domain = (Get-ADRootDSE).DefaultNamingContext
$names = Get-Content $FileIN
$refNotFound = [System.Collections.Generic.List[string]]::new()
$displaynamedetails = foreach($name in $names)
{
if($aduser = Get-ADUser -LDAPFilter "(DisplayName=$name)")
{
$aduser
continue
}
$refNotFound.Add(
"Cannot find an object with DisplayName: '$name' under: $domain"
)
}
$displaynamedetails | Select-Object Name, GivenName, Surname, UserPrincipalName |
Export-Csv -NoTypeInformation -path $fileOUT -Encoding UTF8
$refNotFound # => Here are the users that couldn't be found
Side note, consider stop using $displayname = #() and += for well known reasons.
As for AD Cmdlets, using scriptblock based filtering (-Filter {...}) is not supported and even though it can work, it can also bring you problems in the future.

How to fix A null key is not allowed in a hash literal in PowerShell

I been following this Microsoft doc https://learn.microsoft.com/en-us/powershell/module/exchange/search-unifiedauditlog?view=exchange-ps and I'm trying to write a PowerShell Script that download Audit Log. So far everything is going well but I'm just wondering how can I read User Id from my csv file instead of having a user Id directly in my script?.
This is how my CSV file look right now.
C:\AuditLogSearch\Reference\User.csv
Name Email Id
................................
1. Ronaldo ronaldo#gmail.com KLIEKN
2. Messi messi#gmail.com LEK89K
3. LeBron lebron#gmail.com IKNLM9
I was hard coding like this in the script and it's working fine but
$Global:user = "KLIEKN", "LEK89K", "IKNLM9",
Search-UnifiedAuditLog -SessionId $sessionID -SessionCommand ReturnLargeSet -UserIds $user
I don't think that'll be good idea so I try to do read it from my CSV file like this
$Global:userPath = "C:\AuditLogSearch\Reference\User.csv"
function Log-Search {
Import-Csv $userPath | ForEach-Object -Property #{
$userId = $($_.Id)
}
Search-UnifiedAuditLog -SessionId $sessionID -SessionCommand ReturnLargeSet -UserIds $userId
}
but I'm getting this error
A null key is not allowed in a hash literal.
I'll be really appreciated any help or suggestion.
{} defines a [ScriptBlock] — which is what you'd pass to the ForEach-Object cmdlet to be invoked for each element — whereas #{} defines a [Hashtable]. $userId is $null because you have not assigned a value, so where you have...
#{
$userId = $($_.Id)
}
...you are trying to define a [Hashtable] with an element with a key of $null, hence the error.
There is also no such -Property parameter of ForEach-Object, so when you remove "-Property #", you end up with a valid script...
Import-Csv $userPath | ForEach-Object {
$userId = $($_.Id)
}
This is reading your CSV file but not yet passing the data to your Search-UnifiedAuditLog call. There are several ways to retrieve the Id field of each CSV record, but the shortest transformation from the previous snippet would be...
Import-Csv $userPath | ForEach-Object {
$_.Id
}
...which can be rewritten using the -MemberName parameter...
Import-Csv $userPath | ForEach-Object -MemberName Id
...and then all that's left is to store the pipeline results in $userId...
$userId = Import-Csv $userPath | ForEach-Object -MemberName Id
By the way, the CSV data you posted cannot be readily parsed by Import-Csv. If possible, save your data without the second line and using comma or tab as the delimiter (the latter being read with Import-Csv ... -Delimiter "`t"); otherwise, the script will have to do some manipulation before it can read the data.
Try this perhaps?
$userId=#()
Import-Csv $userPath | ForEach-Object {
$userId += $_.Id
}
or
$userId = Import-Csv $userPath | Select-Object -ExpandProperty Id

Powershell Cascading Foreach to export to Csv

I'm exporting my AAD users to CSV files, works fine with this code.
$allUsers = Get-AzureADUser -All $true
$users = $allUsers |
Select-Object -Property ObjectId,ObjectType,UserPrincipalName,DisplayName,AccountEnabled,AgeGroup,City,CompanyName,ConsentProvidedForMinor,Country,CreationType,Department,DirSyncEnabled,FacsimileTelephoneNumber,GivenName,IsCompromised,ImmutableId,JobTitle,LastDirSyncTime,LegalAgeGroupClassification,Mail,MailNickName,Mobile,OnPremisesSecurityIdentifier,PasswordPolicies,PhysicalDeliveryOfficeName,PostalCode,PreferredLanguage,RefreshTokensValidFromDateTime,ShowInAddressList,State,StreetAddress,Surname,TelephoneNumber,UsageLocation,UserState,UserStateChangedOn,UserType,DeletionTimestamp,AssignedLicenses,AssignedPlans,ProvisionedPlans |
ForEach-Object{
$_.DisplayName = $_.DisplayName -replace "\n", ' ' -replace '"', '`'
$_
}
$users | Export-Csv -Path $TempFileName -NoTypeInformation
$provisionedPlans = $users = $allUsers |
Select-Object -Property ObjectId,DisplayName,ProvisionedPlans
But, ProvisionedPlans comes out as a list, so I would like to export it for each entry in the list as 1 line.
This is a sample of the field
ProvisionedPlans : {class ProvisionedPlan {
CapabilityStatus: Enabled
ProvisioningStatus: Success
Service: MicrosoftCommunicationsOnline
}
, class ProvisionedPlan {
CapabilityStatus: Deleted
ProvisioningStatus: Success
Service: MicrosoftCommunicationsOnline
}
, class ProvisionedPlan {
CapabilityStatus: Deleted
ProvisioningStatus: Success
Service: MicrosoftCommunicationsOnline
}
, class ProvisionedPlan {
CapabilityStatus: Enabled
ProvisioningStatus: Success
Service: SharePoint
}
...}
So bottom line what I would like to see in the output would be
ObjectId,DisplayName,CapabilityStatus,ProvisioningStatus,Service
id1,User1,Enabled,Success,MicrosoftCommunicationsOnline
id1,User1,Deleted,Success,MicrosoftCommunicationsOnline
id1,User1,Deleted,Success,MicrosoftCommunicationsOnline
id1,User1,Enabled,Success,SharePoint
id2,User2,Enabled,Success,Whatever
So please feel free, i'm not a Powershell specialist.
Following your guidance of what you want as a bottom line please see the below adjustments to your script:
$allUsers = Get-AzureADUser -All $true
$Results = Foreach ($user in $allusers){
Foreach($Plan in $user.ProvisionedPlans){
$Plan | Select #{Name = 'ObjectId'; Expression = {$user.Objectid}},#{Name = 'DisplayName';Expression = {$user.DisplayName}},CapabilityStatus,ProvisioningStatus,Service
}
}
$Results | Export-Csv -Path $TempFileName -NoTypeInformation
I am not sure why you are replacing characters in the DisplayName but I have added this to the calculated properties in the Select Statemant.
I have also done this on the iteration of each provisioned plan as this seemed to be the easiest route.
I have removed the initial select with all of the properties as the properties you are interested in are being exported. (If you require all properties I would advise using Select * as it will pull the majority of properties in most cases and will look tidier in the code.)

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.

Adding a ROW for missing Attribute values to Export-CSV

I using the following POWER SHELL script, to extract ( to csv ) managers name , from a "Manager" user attribute.
#This script, , Exports the Manager name of the employee`s in the TXT file.
# users.txt file - contains a simply list of user names ( samaccount-names )
Get-Content D:\powershell\permmisions\Users.txt | Foreach-Object {
Get-ADUser -Identity $_ -Properties Manager | Select-Object name, Manager | Export-Csv D:\Powershell\ADuserinformation\Export-Managers-of-specific-users.csv
-Append
}
The challenge i am facing, is when is on the exported CSV file,
the list "SKIPS" blank value-fields,In case there is no manager set for the user.
And a ROWS is not created , where MANAGER is missing.
What i would like to do , is the script to enter a charcter ( ~ ) for example, where, value is blank.
That way , a row will be created for the blank MANAGER value, on the CSV file
Please help ,
Thanks all in advance.
Note: At least the Name property should exist on all AD users retrieved, so you would get a row even for users where Manager is empty, but with an empty Manager column. If you do need to deal with possibly not all users named in Users.txt actually existing, see Theo's helpful answer.
The simplest approach is to use a calculated property:
Get-ADUser -Identity $_ -Properties Manager |
Select-Object Name, #{ Name='Manager';
Expression={ if ($_.Manager) { $_.Manager } else { '~' } } }
Note:
It is common to abbreviate the key names of the hashtable that defines the calculated property to n and e.
The if statement takes advantage of the fact that an empty string (or $null) evaluates to $false in a Boolean context; for an overview of PowerShell's implicit to-Boolean conversion, see the bottom section of this answer.
In PowerShell [Core] 7.0 or above, you could additionally take advantage of the ternary operator (<condition> ? <valueIfTrue> : <valueIfFalse>) to further shorten the command:
# PSv7+
Get-ADUser -Identity $_ -Properties Manager |
Select-Object Name, #{ n='Manager'; e={ $_.Manager ? $_.Manager : '~' } }
Note: If $_.Manager were to return $null rather than the empty string ('') if no manager is assigned, you could use ??, the PSv7+ null-coalescing operator instead: $_.Manager ?? '~'
Not concise at all, but this allows you to insert more properties of interest in your report, and does some error-checking if the user listed in your input file does not exist:
$report = foreach ($account in (Get-Content D:\powershell\permmisions\Users.txt)) {
$user = Get-ADUser -Filter "SamAccountName -eq '$account'" -Properties Manager -ErrorAction SilentlyContinue
if ($user) {
if (!$user.Manager) { $mgr = '~' }
else {
# the Manager property is the DistinghuishedName for the manager.
# if you want that in your report, just do
$mgr = $user.Manager
# if you want the Name for instance of that manager in your report,
# comment out the above line and do this instead:
# $mgr = (Get-ADUser -Identity $user.Manager).Name
}
# now output an object
[PsCustomObject]#{
UserName = $user.Name
Manager = $mgr
}
}
else {
Write-Warning "User '$account' does not exist"
}
}
# output on screen
$report | Format-Table -AutoSize
# output to CSV file
$report | Export-Csv -Path 'D:\Powershell\ADuserinformation\Export-Managers-of-specific-users.csv' -NoTypeInformation