Reading from and appending to CSV or XLSX in Powershell - powershell

For the last few years I've been using Powershell in my limited capacity to perform lookups in Active directory to fill in information that's missing from a lot of different reports. It usually involves get-aduser to find company, location, account status, manager, and a handful of other properties. What I'll do is take a report that's been handed to my team that is ALWAYS lacking key info and is usually anywhere from 500 to 5000 rows, export the userID (or whatever property I'm searching by) to a text file and reference that in a foreach loop to get the info I need. I'll then export that data to CSV, copy it to the original report and make sure whatever property I'm searching on matches at the top middle and bottom of the sheet then delete what's extra and go from there.
For reference, here's what I'm using now.
$TotalRows = (get-content '[path to file]').Length
foreach($EmployeeID in Get-Content '[path to file]') {
Write-Progress -Activity 'Processing IDs' -Status 'Searching Active Directory' -PercentComplete ((($rowcounter++) / $TotalRows) * 100)
Get-ADUser -Filter {EmployeeID -eq $EmployeeID} -Properties * | Select-Object EmployeeID,SamAccountName,UserPrincipalName, company, l, enabled, department, title, manager | Export-Csv -NoTypeInformation -Path '[path to output directory]\output.csv' -Append
}
This does a good job of things, but it requires a lot of copy/paste work and checking to make sure things still line up.
The ask... I want to be able to take all the garbage work out of the middle. I want to take a csv, tell Powershell to look in, lets say, E7 for a UPN, execute a get-aduser query then export the results to E8 and beyond based on how many properties I'm returning. The script would then chew through the rest of the sheet and export get-aduser info for each row.

Related

Windows PowerShell script for verifying several Active Directory groups exist

I need to run a PowerShell script to verify that a huge list of Active Directory groups exist based on an .xlsx file. I would also like to see the owners of the AD groups if possible.
I can run separate scripts, if needed. Please help.
Reading and writing to an excel file from PowerShell is its own adventure, but one that is heavily documented if you google it.
For the query you are asking, its important to know if you have the group's Display Name or the group's SamAccountName. If you have both, you could put an -OR in the filter.
Here is a quick example of how it could work based on a text list.
$list = 'Cross Functional Team
Security Group
Bobs Team' -split([Environment]::NewLine)
$SelectSplat = #{
Property = #{n='Name';E={$_}},
#{N='Found';e={[Bool](Get-adgroup -Filter {DisplayName -eq $_})}},
#{N='ManagedBy';e={Get-adgroup -Filter {DisplayName -eq $_} -Properties ManagedBy | %{(Get-ADUser -Identity $_.ManagedBy).Name}}}
}
$list | Select-Object #SelectSplat

How to change ManagedBy owner from one user to another one for 150+ groups using power shell

I would like to change the Active Directory Group tab ManagedBy user to another one. With PowerShell script, I exported the groups with the old owner (>150) to a csv file. Now I need to change the owner of those groups using the csv file as input.
I don`t have much experience with scripting, I appreciate any help.
Thanks!
The task is very easy with PowerShell. You didn't show an example of the CSV data you exported so an example may not be exact. However, I assume you exported the default output of Get-ADGroup it might look something like this
(Import-Csv C:\temp\managedBy.csv).DistinguishedName| Set-ADGroup -ManagedBy <NewManager's DN>
Note: I like to use the DistinguishedName for these things but samAccountName should also work.
(Import-Csv C:\temp\managedBy.csv).samAccountName | Set-ADGroup -ManagedBy <NewsamAccountName>
Note: Again with the assumption that your Csv data is a direct export Get-ADGroups's output. You cannot pipe Import-Csv directly to Get/Set-ADGroup as the latter will have trouble determining which property to bind to the -Identity parameter.
However, I would point out you really don't need the intermediate Csv file. You can query AD directly for groups managed by the old manager and pipe that to a command to change the owner.
Get-ADGroup -Filter "ManagedBy -eq '<OldOwner'sDN>'" |
Set-ADGroup -ManagedBy "<NewOwner'sDN"
Note: Again you may be able to get away with using the samAccountName instead of the DN.
Note: You can add the WhatIf parameter to the Set-ADGroup` command to preview what will happen before actually running it.

Optimize Get-ADUser filter

In AD, I'm trying to identify user accounts where the same EmployeeID value is populated in 2 or more records. Below is my piece of code (Credit: I'm using a Show-Progress function defined here) and the Get-ADUser command alone has taken more than 2 hours to fetch all the records. The other steps (2 to 5) have been pretty quick. While I've completed the work, I'm trying to know if this could've been done more efficiently with PowerShell.
Get-ADUser -LDAPFilter "(&(ObjectCategory=Person)(objectclass=user)(employeeid=*))" -Properties $properties -Server $server_AD_GC -ResultPageSize 1000 |
# *ISSUE HERE*
# The Get-ADUser extract process seems to work very slow.
# However, it is important to note that the above command will be retrieving more than 200K records
# NOTE: I've inferred that employeeid is an indexed attribute and is replicated to GlobalCatalogs and hence have used it in the filter
Show-Progress -Activity "(1/5) Getting AD Users ..." |
select $selectPropsList -OutVariable results_UsersBaseSet |
Group-Object EmployeeID |
Show-Progress -Activity "(2/5) Grouping on EmployeeID ..." |
? { $_.Count -gt 1 } |
Show-Progress -Activity "(3/5) Filtering only dup EmpID records ..." |
select -Exp Group |
Show-Progress -Activity "(4/5) UnGrouping ..." |
Export-Csv "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv" -NoTypeInformation |
Show-Progress -Activity "(5/5) Exporting ..." |
Out-Null
PS: I've also tried to first export all the user accounts to a csv file and then post-process with Excel but I had to frown because of the size of the dataset and it was both time and memory crunching.
Any suggestion is highly appreciated.
Since we don't know what is in $properties or $selectPropsList, your question is really only about finding out to which users the same EmployeeID has been issued, right?
By default, Get-ADUser already returns these properties:
DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname, UserPrincipalName
So all you need extra is the EmployeeID I guess. Trying to collect LOTS of properties does slow down, so keeping this to a bare minimum helps to speed things up.
Next, by using the Show-Progress script you have linked to, you will slow down the execution of the script considerably. Do you really need to have a progress bar?
Why not simply write the lines with activity steps directly to the console?
Also, piping everything together doesn't help in the speed department either..
$server_AD_GC = 'YourServer'
$selectPropsList = 'EmployeeID', 'Name', 'SamAccountName', 'Enabled'
$outFile = "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv"
Write-Host "Step (1/4) Getting AD Users ..."
$users = Get-ADUser -Filter "EmployeeID -like '*'" -Properties EmployeeID -Server $server_AD_GC -ResultPageSize 1000
Write-Host "Step (2/4) Grouping on EmployeeID ..."
$dupes = $users | Group-Object -Property EmployeeID | Where-Object { $_.Count -gt 1 }
Write-Host "Step (3/4) Collecting duplicates ..."
$result = foreach ($group in $dupes) {
$group.Group | Select-Object $selectPropsList
}
Write-Host "Step (4/4) Exporting ..."
$result | Export-Csv -Path $outFile -NoTypeInformation
Write-Host "All done" -ForegroundColor Green
P.S. Get-ADUser already returns user objects only, so there is no need for the LDAP filter (ObjectCategory=Person)(objectclass=user). Using -Filter "EmployeeID -like '*'" is probably faster
This answer complements Theo's helpful answer and focuses on showing progress during the operation:
The linked Show-Progress function, which is the latest as of this writing:
has an outright bug, in that it doesn't pass pipeline input through (the relevant line is accidentally commented out)
is conceptually flawed in that it doesn't use a process block, which means that all pipeline input is collected first, before it is processed - which defeats the idea of a progress bar.
Therefore, you Show-Progress calls won't show progress until the previous command in the pipeline has output all of its output. A simple alternative is to break the pipeline into separate commands and to simply emit one progress message before each command, announcing the next stage of processing (rather than per-object progress) as shown in Theo's answer.
Generally, there is no way to show the progress of command-internal processing, only the progress of a command's (multi-object) output.
The simplest way to do this via a ForEach-Object call in which you call
Write-Progress, but that comes with two challenges:
In order to show a percent-complete progress bar, you need to know how many objects there will be in total, which you must determine ahead of time, because a pipeline cannot know how many objects it will receive; your only option is to collect all output first (or find some other way to count it) and then use the collected output as pipeline input, using the count of objects as the basis for calculating the value to pass to Write-Progress -PerCentComplete.
Calling Write-Progress for each object received will result in a significant slowdown of overall processing; a compromise is to only call it for every N objects, as shown in this answer; the approach there could be wrapped in a properly implemented function a la Show-Progress that requires passing the total object count as an argument and performs proper streaming input-object processing (via a process block); that said, the mere act of using PowerShell code for passing input objects through is costly.
Conclusion:
Percent-complete progress displays have two inherent problems:
They require you to know the total number of objects to process beforehand (a pipeline has no way of knowing how many objects will pass through it):
Either: Collect all objects to process in memory, beforehand, if feasible; the count of elements in the collection can then serve as the basis for the percent-complete calculations. This may not be an option with very large input sets.
Or: Perform an extra processing step beforehand that merely counts all objects without actually retrieving them. This may not be practical in terms of the additional processing time added.
Object-by-object processing in PowerShell code - either via ForEach-Object or an advanced script/function - is inherently slow.
You can mitigate that somewhat by limiting Write-Progress calls to every N objects, as shown in this answer
Overall it's a tradeoff between processing speed and the ability to show percent-complete progress to the end user.

Is there a way to export the variable in a ForEach loop to my CSV output on each line

I am attempting to run a script against our AD that spits out the AD Users in a group, for all the groups in a list. Essentially attempting to audit the groups to find the users. I have a functioning script, except I have no way to determine when the output of one group ends, and where the output of the next begins.
I have tried looking for previous examples, but nothing fits exactly the method I am using, and with me just dipping my toes into powershell I have not been able to combine other examples with my own.
$groups = Get-Content "C:\Folder\File_with_lines_of_ADGroup_Names.txt"
foreach ($group in $groups) { Get-ADGroupMember -Identity "$group" | Where-Object { $_.ObjectClass -eq "user" } | Get-ADUser -Property Description,DisplayName | Select Name,DisplayName,Description,$group | Export-csv -append -force -path "C:\Folder\File_of_outputs.csv" -NoTypeInformation }
Right now the problem lies with getting the $group variable to be exported along with the Name, DisplayName, and Description of each user returned. This is the best way I can think of to tag each user's group and keep all the results in a single file. However, only the first line of results works which is the HEADERS of the CSV, and everything after it is either listed as "Microsoft.ActiveDirectory.Management.ADPropertyValueCollection"or simply blank after the first group of results.
Hoping someone can show me how to easily add my variable $group to the output for each user found for filtering/pivoting purposes.
Thanks and let me know if you have questions.
I believe what you are after are calculated properties on your select statement.
Select Name,DisplayName,Description,$group
Should Probably be something like
Select Name,DisplayName,Description,#{n='Group'; e={$group};}
See also https://serverfault.com/questions/890559/powershell-calculated-properties-for-multiple-values

PowerShell: Compare CSV to AD

I'm fairly new to PowerShell and I'm posting this on many forums but I've had success with programming assistance from here before and although this isn't strictly programming, I was hoping someone might know the answer.
My organization had about 5,300 users we needed to disable for a client. Someone decided the best use of our time was have people go through AD and disable them one at a time. Soon as I got wind of this I put a stop to it and used PowerShell to take the CSV list we already had, and ran a cmdlet to disable all of the users in the CSV list.
This appeared to work, but I wanted to run a comparison. I want to compare the users from the CSV file, to the users in AD, and confirm that they are all disabled without having to check all 5300 individually. We checked about 60 random ones to verify my run worked, but I want to make sure none slipped through the cracks.
I've tried a couple scripts and I've tried some variations of cmdlets. None of the scripts I tried even worked, spammed with errors. When I try to run a search of AD either using get-content or import-CSV from the csv file, when I export its giving me about 7600 disabled users (if I search by disabled). There were only 5300 users in total, so it must be giving me all of the disabled users in AD. Other cmdlets i've run appear to do the same thing, its exporting an entire AD list instead of just comparing against my CSV file.
Any assistance anyone can provide would be helpful.
Without knowing the exact structure of your CSV I'm going to assuming it is as such:
"CN=","OU=","DC="
"JSmith","Accounting","Foo.com"
"BAnderson","HR","Foo.com"
"JAustin","IT","Foo.com"
That said, if your first field actually has CN= included (i.e. "CN=JSmith","OU=Accounting","Foo.com") you will want to trim that with .TrimStart("CN=").
$ToRemove = Import-CSV UserList.csv
$UserList=#()
ForEach($User in $ToRemove){
$Temp = ""|Select "User","Disabled"
$Temp.User = $User.'CN='
If((Get-aduser $Temp.User -Prop Enabled).Enabled){$Temp.Disabled='False'}else{$Temp.Disabled='True'}
$UserList+=$Temp}
$UserList|?{$_.Disabled -eq 'False'}
That loads the CSV into a variable, runs each listing through a loop that checks the 'CN=' property, creates a custom object for each user containing just their name and if they are disabled, and then adds that object to an array for ease of use later. In the end you are left with $UserList that lists everybody in the original CSV and if they are disabled. You can output it to a file, filter it for just those that are still enabled, or whatever you want. As noted before if your CSV actually has CN=JSmith for each line you will want to update line 5 to look as such:
$Temp.User = $User.'CN='.TrimStart("CN=")
If you don't have any headers in the CSV file you may want to inject them. Just put a line at the top that looks like:
CN=,OU=,DC=
Or, if you have varying OU depths you may be better off doing a GC and then running each line through a split, taking the first part, trimming the CN= off the beginning, and checking to see if they are disabled like:
GC SomeFile.CSV||%{$_.split(",")[0].trimstart("CN=")|%{If((get-aduser $_ -prop enabled).enabled){"$_ is Enabled"}else{"$_ is Disabled"}}}
Assuming your CSV has a column called DN you can run the following which will return all users from your spreadsheet which are enabled
import-csv YourUsersCSV.csv | Get-ADUser -Filter
{DistinguishedName -eq $_.DN } |
where{$_.enabled -eq $true} |
Select-Object -Property DistinguishedName,samaccountname,enabled