Trying to find a way to speed up this powershell script - powershell

I have this script that works fine (output looks good), but it is taking longer than 12 hours now. There are 34220 records in the csv and it's only now on 2110. Maybe I need to load up all the user data first, then compare to the csv file? Thx for help...
import-module ActiveDirectory
$CCure = Import-csv C:\Scripts\CCure\CCure-Personnel-enabled.csv
ForEach ($Row in $CCure) {
[string]$ID = $Row.ObjectID
[string]$Name = $Row.Name
[string]$EmpID = $Row.Int5
If ($EmpID.Trim() -ne "0") {
$User = Get-ADUser -LDAPFilter "(&(&(&(objectclass=user)(objectcategory=person)(!userAccountControl:1.2.840.113556.1.4.803:=2))))((employeeId=*$EmpID))" -SearchBase 'DC=Enterprise,DC=mycompany,DC=org' -Properties SamAccountName,DisplayName,EmployeeId,enabled |
Select #{Name="CCure ObjectID";Expression={$ID}},SamAccountName,DisplayName,#{Name="CCure Name";Expression={$Name}},EmployeeId,#{Name="CCure Int5 Row";Expression={$EmpID}},enabled | Export-csv c:\scripts\ccure\EmployeeIds4-10-2016.csv -NoTypeInformation -append
}
}

Maybe I need to load up all the user data first, then compare to the csv file?
That's exactly what you need to do!
Since you want to correlate the users in the CSV by the EmployeeId attribute, I'd recommend pulling out all the (enabled) users that have the EmployeeId populated, and then store them in a hashtable where the EmployeeId is used as the key:
$ADUserTable = #{}
Get-ADUser -LDAPFilter "(&(!userAccountControl:1.2.840.113556.1.4.803:=2)(employeeId=*))' -SearchBase 'DC=Enterprise,DC=mycompany,DC=org' -Properties SamAccountName,DisplayName,EmployeeId |ForEach-Object {
$ADUserTable[$_.EmployeeId] = $_
}
Then, as you iterate over the rows in the CSV, lookup the user in the hashtable instead of searching AD again:
$ExistingUsers = ForEach ($Row in $CCure) {
# Import-Csv always creates string properties anyways
$ID = $Row.ObjectID
$Name = $Row.Name
$EmpID = $Row.Int5.Trim()
if ($EmpID -ne "0" -and $ADUserTable.ContainsKeys($EmpID))
{
$ADUserTable[$EmpID] |Select #{Name="CCure ObjectID";Expression={$ID}},SamAccountName,DisplayName,#{Name="CCure Name";Expression={$Name}},EmployeeId,#{Name="CCure Int5 Row";Expression={$EmpID}}
}
}
Do NOT export them to Csv until AFTER you've collected all the information - otherwise you're opening, writing to and closing the same file 35000 times!
So, at the very end:
$ExistingUsers |Export-csv c:\scripts\ccure\EmployeeIds4-10-2016.csv -NoTypeInformation
This will undoubtedly speed up execution of your script
Note: I've removed the Enabled property from Get-ADUser and Select-Object. Your LDAP Filter already guarantees that only Enabled users are returned, so I don't really see any value in adding it to the CSV

Related

PowerShell: If statements and using blank cells in Import-CSV

I am having difficulty using an if statement with blank cells in my CSV file. I'm attempting to write an onboarding script and have it pull info from an xlsx HR fills out (IT copies needed rows into CSV that is used in script). In order to get the OU path, I use the department names. However, some users have a sub-department and others do not, these fields are left blank in the xlsx that HR sends. I have it working by inputting a N/A in those fields however if another tech doesn't know to do that in the CSV file the script will fail. So I would like to get it working with the blank fields.
This is what im trying when not using the N/A in the CSV field
foreach ($obj in $onboardcsv){
$dep = "$($obj.department)"
$sdep = "$($obj.subDepartment)"
if ($null -eq $obj.subDepartment){
$ou = Get-ADOrganizationalUnit -Filter {name -like $dep} -SearchBase "OU=User,OU=OU,DC=DOMAIN,DC=com"
}
else{
$ou = Get-ADOrganizationalUnit -Filter {name -like $sdep} -SearchBase "OU=User,OU=OU,DC=DOMAIN,DC=com"
}
Any help would be appreciated!
To rephrase your question, you just want to search where SubDepartment isn't empty?
Without modifying too much of your code, you can make use of the static method of ::IsNullOrWhiteSpace() provided in the [string] class to evaluate against the emptiness:
Using -Not reverses the result of [string]::IsNullOrWhiteSpace($obj.subDepartment).
foreach ($obj in $onboardcsv)
{
$department = if (-not [string]::IsNullOrWhiteSpace($obj.subDepartment)) {
$obj.subDepartment
}
else {
$obj.department
}
Get-ADOrganizationalUnit -Filter "Name -like '$department'" -SearchBase "OU=User,OU=OU,DC=DOMAIN,DC=com"
}
So, testing against the subDepartment first, if $obj.subDepartment is not null, assign it to $department. This will allow the use of just one variable for both properties, and no code copying necessary.
Thanks to #Santiago for a sanity check.
Something like this would work.
$ou = "searching by sub department"
$department = if (!($user.subDepartment)) {
#subdepartment is blank
#searching by department
$ou = "searching by department"
}
$ou

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.

Export Powershell output to CSV

I have the following code intended to take a list of user names and output a CSV report of username - GroupMembership. At the command line the output looks great as i get "name" on the left and recursive "group memberships" on the right (see pic http://i.stack.imgur.com/zLxUR.jpg for command line output, sorry can't post imbedded Pics yet)
I would like to have the output written to a CSV file with the same format, namely Username in one column and GroupMemberships in the second column.. Original code from: http://thesurlyadmin.com/2013/03/21/get-a-users-group-memberships/ with a few small changes.
Param (
[Parameter(Mandatory=$true,ValueFromPipeLine=$true)]
[Alias("ID","Users")]
[string[]]$User
)
Begin {
Try { Import-Module ActiveDirectory -ErrorAction Stop }
Catch { Write-Host "Unable to load Active Directory module, is RSAT installed?"; Break }
}
Process {
ForEach ($U in $User)
{ $UN = Get-ADUser $U -Properties MemberOf
$Groups = ForEach ($Group in ($UN.MemberOf))
{ (Get-ADGroup $Group).Name
}
$Groups = $Groups | Sort
ForEach ($Group in $Groups)
{ New-Object PSObject -Property #{
Name = $UN.Name
Group = $Group
}
}
}
}
I tried using this "$PSObject | Export-CSV C:\Scripts\GroupMembershipList.csv" but it only writes the first line to the CSV and nothing after that.
Nate,
In Powershell v3.0, the Export-CSV cmdlet introduced the -Append parameter.
Reference: http://technet.microsoft.com/en-us/library/hh849932.aspx
Not knowing the version of Powershell you are using, this may require an update on your side to make use of the new functionality.
In my own cases, I generally see the opposite issue if I forget to -Append to my CSV; I will only end up with the LAST entry as opposed to just the first.
I won't claim this to be your fix, but might be worth a shot...
Example: $PSObject | Export-CSV C:\Scripts\GroupMembershipList.csv -Append

Powershell Nested Hashtables - Export to file

Im pulling out information from AD to output alot of information to be handled at a later point.
I need to export this to a csv or something so i can have a rollback ( in effect import file so i can handle each object like i can with the hashtable below. Especially that inside $_.MemberOf).
The hashtable im struggling with handling is:
$logging1 = #{
MemberOf="$users.MemberOf"
OriginalOU="$Ou.DistinguishedName"
DisabledWhen="$descriptionDisabled"
}
$logging = #{$users.SamAccountname = $logging1}
what the rest of the script does is(that isnt pasted ) is:
iterate through a bunch of ou's. Getting users last logged in -90 days ago or more then passing them to the HashTable in question which i like to append to a file to be imported at a later time.
I have been googling for hours without getting somewhere
here is the complete script:
import-module ActiveDirectory
$descriptionDisabled = get-date -Format yyyyMMdd
$Loggdir = "C:\temp"
$array = #{}
$loggname = get-date -f yyyyMMdd
$90days = ((get-date).AddDays(-90))
$searchBase = 'OU=someou4,OU=someou3,OU=someou2,OU=someou1,DC=name,DC=NO'
$ExclusionList = Someexlutions
$OUlist = Get-ADOrganizationalUnit -SearchBase $searchBase
foreach ($Ou in $OUlist)
{
$ExpiredADusers = get-aduser -Filter {(LastlogonDate -le $90days)
-and (Enabled -eq $True)}
-SearchBase $ou.distinguishedname
-Properties *
#Get information about every user and their groupmembership
foreach ($Users in $ExpiredADusers)
{
$users = get-aduser -Identity $users.SamAccountName
-Properties *
| Select-Object -Property SamAccountName, MemberOf,
DistinguishedName, ObjectGUID
$logging = #{$person.SamAccountName = #{
MemberOf=$person.MemberOf;
OriginalOU=$Ou.DistinguishedName;
DisabledWhen="$descriptionDisabled"}}
$Array += $Logging
}
}
$array | export-clixml -path somepath -noclobber
EDIT SOLVED
Solved problem and original script in question has been updated to handle Nested hashtables
Original problem
I think you can understand what im trying to do.
Im making a rollback file incase needed.
So the exported file need to be easy to import. when using hashtables you can use . notations and each objects under memberof is treated as an object
What the rollback needs is so i can iterate through the imported info to move the user back to its original OU placement and restore membership
I think EBGreen really hit the nail on the head here. You need an array of custom objects, and then you can just export it to a CSV like you want to. It's a really minor change in code too.
$Array = #()
$ForEach($User in $Users){
$logging1 = New-Object PSObject -Property #{
MemberOf=$user.MemberOf
OriginalOU=$Ou.DistinguishedName
DisabledWhen=$descriptionDisabled
}
$Array += $Logging1
}
Edit: Hm, so you want to be able to export and import full objects. You don't want a CSV then because you have nested arrays, and a CSV is not designed to handle that for export and import. You need XML, so, as mentioned above, you need to use Export-Clixml and Import-Clixml since XML can handle nested arrays. Just pipe the array to it once the array has all your data and you should be all set.
Edit2: The hashtables within hashtables issue... Ok, so we had it as MemberOf="$user.MemberOf" and that's the issue. It is converting it to a string, so it is expanding the entire $User variable, and tacking .MemberOf to the end of it. We don't really want to do it in this case, but if you want to access a property of an object from within doublequotes you need to put $() around it. For example if you wanted to include the user's distinguishedname as a part of human friendly output you could do something like:
Write-Output "$($Users.Name)'s distinguished name is: $($users.distinguishedname)"
Which would output something like:
TMTech's distinguished name is: CN=TMTech,OU=Awesome,OU=Administrators,DC=Digital,DC=Ghost,DC=net

Get sAMAccountNames from CSV of Employee IDs powershell

Im not sure why I cannot get this to work, but I have a .csv of employee ID's and I want to add them to an AD group, but I cannot get this to work
Function Sync-ADGroup {
$userIDs = Import-CSV $CSV
foreach($ID in $IDs){
Get-ADUser -Filter "EmployeeID -eq '$ID'" -Properties SAMAccountName
}
}
Then I would add them to the group, but I cannot it to return the ADUserObject. Not sure what I am missing.
You need to reference the property name (column) of the user as it appears in the csv file. For example, if the value in the file is under the EmployeeID header:
foreach($userID in $userIDs)
{
Get-ADUser -Filter "EmployeeID -eq $($userID.EmployeeID)" -Properties SAMAccountName
}
For the above to work your csv file needs to look like:
EmployeeID
1234
2345
3456
Undeclared collection variable is used in foreach loop.
$userIDs = Import-CSV $CSV # Load stuff to "userIDs"
foreach($ID in $IDs){ # Enumerate "IDs", oops, should be "userIDs"
As $IDs is empty a variable, the loop doesn't do much.
In order to avoid this kind of errors, use the strict mode: Set-PSDebug -Strict. This will rise an error for using undeclared variable. (Rant: the strict mode should be the default in Powershell. I set it in my profile and all the scripts I write for good measure.)