Export user from multiple groups - powershell

Good Morning Guys,
I write this script to export multiple users in multiple groups in a CSV file.
In addition to not exporting anything, I cannot insert a column in the CSV where the user's appatenency group is specified. Just the one with the name "vpn-*", like vpn-users
Powershell Version 4.0
$Data = $UserData = #()
$GroupName = Get-ADGroup -Filter {name -like "VPN-*"} -Properties Name | Select-Object Name
Foreach ($group in $GroupName)
{
$UserData = Get-ADGroupMember -Identity {$group}
Where objectClass -eq 'user' |
Get-ADUser -Properties Name, UserPrincipalName, description, Enabled |
Select-Object Name, UserPrincipalName, description, Enabled
}
$UserData | export-csv "c:\members2.csv"

I see a number of issues with the posted code. Some are mere preferences, but others look like they would affect the outcome.
-Properties Name isn't needed in the initial Get-ADGroup command. The Name property is returned by default.
Respectful to #Theo's comment re: -ExpandProperty, however, I prefer not to use that unnecessarily. You don't even need the first Select-Object command, just reference the property and it will unroll and return a string array. Property unrolling is supported as of PowerShell Version 3.
Your argument to the -Filter parameter should be a string. I myself have a hard time breaking the habit of wrapping those queries in script blocks {...}, however, there are some specifics of the AD cmdlets, and using a string is a best practice. Basically, the argument takes a string type, if you give it a script block it will convert it to a string and that process may not always work as desired.
It's better to store your AD properties in an array you can cite multiple times. Normally you would've only needed to add 'Description' but considering you were also using it in a later Select-Object command it makes more sense to add all the desired properties to an array.
Personally, I prefer not to use simplified syntax. I don't know what the community's opinion is on that but I rewrote using my preference.
You were also missing |' before the Where-Object command.
And, yes you would have to move the Export-Csv inside the loop to output data per loop iteration. Using the -Append parameter to avoid overwriting it on every iteration.
Untested Example:
$ADProps = 'Name', 'UserPrincipalName', 'description', 'Enabled'
$GroupName = (Get-ADGroup -Filter "name -like 'VPN-*'" ).Name
Foreach ( $Group in $GroupName )
{
$UserData =
Get-ADGroupMember -Identity $Group |
Where-Object{ $_.objectclass -eq 'user' } |
Get-ADUser -Properties $ADProps |
Select-Object ($ADProps += #{Name = 'Group'; Expression = { $Group }})
$UserData | export-csv "c:\members2.csv" -NoTypeInformation -Append
}

Related

Powershell AD: filter description -like $variable => contains $variable

My task include to filter all users names in group and subgroup in AD. Continue to filter the computers and show just those, which contains filtered names.The problem is, that description includes also other characters like space or "NEW".
My code:
foreach ($file in Get-ADGroupMember -Identity GroupName -Recursive) {Get-ADComputer -Filter 'Description -like $file.name' -Property Name,Description | Select -Property Name,Description}
It would be great to just add * or change -like to -include :D But...
My begginers question is: How to write the code to see all results, not just the ones which match exactly the $file.name?
Thank you for ur time!
Your initial problem was in the Filter you used. With the correct quoting and using the sub-expression operator $() that fixed it.
However, as promised in my comment, here's what I mean on how you can create a report of group members (both users, computers and if you like also subgroups).
Since all objects returned from the Get-ADGroupMember cmdlet have an .objectClass property, you can use that to determine what next Get-AD* cmdlet you can use.
Here, I'm capturing the collected objects output in the foreach() loop in a variable that you can show on screen, or save as Csv file you can open in Excel for instance.
$groupName = 'GroupName'
$result = foreach($adObject in (Get-ADGroupMember -Identity $groupName -Recursive)) {
# use the proper Get-AD* cmdlet depending on the type of object you have
switch ($adObject.objectClass) {
'user' {
$adObject | Get-ADUser -Properties Description | Select-Object Name, Description, #{Name = 'Type'; Expression = {'User'}}
}
'computer' {
$computer = $adObject | Get-ADComputer -Properties Description
# you want to output only the computers where the Description property holds the computer name
if ($computer.Description -like '*$($computer.Name)*') {
$computer | Select-Object Name, Description, #{Name = 'Type'; Expression = {'Computer'}}
}
}
# perhaps you don't want subgroups in your report, in that case just remove or comment out the next part
'group' {
$adObject | Get-ADGroup -Properties Description | Select-Object Name, Description, #{Name = 'Type'; Expression = {'Group'}}
}
}
}
# show the result on screen
$result | Format-Table -AutoSize
# save the result as Csv file
$outFile = Join-Path -Path 'X:\Somewhere' -ChildPath ('{0}_members.csv' -f $groupName)
$result | Export-Csv -Path $outFile -NoTypeInformation -UseCulture
The -UseCulture switch makes sure the Csv file uses the delimiter character your local Excel expects. Without that, a comma is used
Interesting reads:
about_Operators
Adam the Automator
Learn Powershell | Achieve More
and of course StackOverflow

Having some trouble filtering Get-ADUser using whenCreated powershell command

I can use some help structing/nesting the below powershell code to get the desired outcome.
When I use the command:
Get-ADGroupMember -Identity $groupname |
Get-ADUser -Properties * -Erroraction Ignore |
select #{N='UserName';E={$_.UserPrincipalName}} |
Export-csv $filename -NoTypeInformation
It works as expected and only selects people from the AD group $groupname, but when I modify the code and try to filter for only NEW accounts based on when the account was created, it's now including user accounts that are NOT in that group
Get-ADGroupMember -Identity $groupname |
Get-ADUser -Filter { whenCreated -ge $when } -Properties * -Erroraction Ignore |
select #{N = 'UserName'; E = { $_.UserPrincipalName } } |
Export-csv $filename -NoTypeInformation
I'm not sure why it is now including all new user accounts in our AD, even some outside of the group $groupname
You cannot use -Filter while piping in objects. If you are piping in objects Get-ADUser will assume you want to get them. This boils down to a parameter binding error, however, since you've set the -ErrorAction Ignore you aren't getting the error feedback that would have said so.
You should have any problem getting AD Users that we know exist by virtue of their group membership. So, I'd question if you really need to ignore errors. That said, Removing -ErrorAction Ignore will not solve the parameter binding issue. For that, I'm afraid you'll have to resort to post-filtering with a Where{} clause. For example:
$When = (Get-Date "12/31/2020").ToUniversalTime()
Get-ADGroupMember -Identity $groupname |
Get-ADUser -Properties WhenCreated |
Where-Object{ $_.WhenCreated -ge $when }
Select-Object #{Name = 'UserName'; Expression = { $_.UserPrincipalName } } |
Export-csv $filename -NoTypeInformation
Note: The property is stored in UTC, so by converting the local time we're interested in we should get the correct results.
Note: You do not need to get all the properties. UserPrincipalName is included in the default set. To post-filter you will have to add WhenCreated to the results.
An Aside: Try not to use script blocks for the -Filter argument. If you look at Get-ADUser help documentation you'll find the parameter is actually string typed. As such specifying a script block requires recasting under the hood and can lead to issues. so, if you were to use the -Filter parameter in this project or elsewhere just use a regular string, like: -Filter "Name -like '*steve*'"
Another Aside: A [DateTime] such as returned by Get-Date cannot be directly used in a -Filter argument. This likely has to do with If, when, and how cmdlet is manipulating the WhenCreated. The Value of the WhenCreated LDAP attribute is stored more like "20200820040000.Z", so you can adjust to use as the -Filter or -LDAPFilter argument respectively like below:
$when = (Get-Date '8/20/20' ).ToUniversaltime().ToString('yyyMMddHHmmss.Z')
Get-ADUser -Filter "WhenCreated -ge '$when'"
Or with -LDAPFilter
Get-ADUser -LDAPFilter "(whencreated>=$when)"
I'll follow-up with documentation on the filter-able properties if I can find it.
Update:
Based on comments from #SantiagoSquarzon, you may not want to use Get-ADGroupMember at all, as you may be passing non-user objects to Get-ADUser. Combining up his suggestion, an example may look like:
$when = (Get-Date '8/20/20' ).ToUniversaltime().ToString('yyyMMddHHmmss.Z')
$groupDN = (Get-ADGroup -Identity $groupname).DistinguishedName
Get-ADUser -LDAPFilter "(&(memberOf=$groupDN)(whencreated>=$when))" |
Select-Object #{Name = 'UserName'; Expression = { $_.UserPrincipalName }} |
Export-csv $filename -NoTypeInformation

Get SamAccountName from list of various names

I have a list of names taken from Oracle that Im trying to find the SamAccountName for. The file is a CSV and some names are "last, first" or "last, first middle initial" and some have three or four names like "alpha bravo charlie delta". The names in the list are likely not the same as listed in AD. Trying to figure out how to sort through this to find AD accounts. The code I currently have is not producing any results.
Import-Module ActiveDirectory
Import-Csv "\\server\users\folder\Oracle_HR.csv"
ForEach-Object{
Get-ADUser -Filter { Name -like "'$($_.name)'"} -Properties Name |
Select-Object Name,SamAccountName |
Export-CSV "\\server\users\folder\Oracle_ADs.csv" -NoTypeInformation
}
The answers by Gabriel Luci and Mathias R. Jessen give good advice on "fuzzy" filtering of AD users.[1]
However, your primary problem is that your ForEach-Object call is not receiving pipeline input, because you haven't connected it to output from the Import-Csv call.
Simply join the two commands with |:
Import-Csv "\\server\users\folder\Oracle_HR.csv" | ForEach-Object { # ...
Secondarily, your -Filter argument { Name -like "'$($_.name)'"} mistakenly applies two layers of quoting and is missing wildcard characters, given that -like compares against the entire field, yet you want substring matching.
Since it's better to avoid the use of script blocks ({ ... }) as -Filter arguments, use a string:
"Name -like `"*$($_.name)*`"" # Note the enclosing '*' to match substrings
Note that I've used embedded " quoting (escaped as `") rather than ' quoting, so as not to break the filter with names that contain ', such as O'Malley.
That said, if, as your question suggests, the names in your CSV data aren't direct substrings of the AD users' .Name property values, the above filter will not be enough, and even the ANR (Ambiguous Name Resolution) technique shown in the linked answers may not be enough.
Thirdly, your Export-Csv call is misplaced: because it is inside the ForEach-Object script block, the same output file gets overwritten in every iteration.
Restructure your command as follows:
Import-Csv ... | ForEach-Object { ... } | Export-Csv ...
Optional reading: ForEach-Object behavior when not providing pipeline input:
The associated script block is executed once.
$_, the automatic variable that contains the current input object, is $null.
[1] Note that the search term in the LDAP filter may need escaping ; per this article, the characters * ( ) \ NUL require escaping and must be escaped as \<hh>, where <hh> is the two-digit hex representation of the char's ASCII code (e.g., * must be escaped as \2A):
$escapedName = -join $(foreach ($c in [char[]] $_.name) { if ('*', '\', '(', ')', "`0" -contains $c) { '\' + ([int] $c).ToString('X2') } else { $c } })
Get-ADUser -LDAPFilter "(anr=$escapedName)"
With $_.name containing string "James* (Jimmy) Smith\Smyth`0", $escapedName would evaluate to literal James\2A \28Jimmy\29 Smith\5CSmyth\00
Keep in mind that the property names in PowerShell are not named the same as the attributes in AD. The Name property corresponds to both the name and cn attributes in AD (both attributes are always the same).
There is also DisplayName (displayName in AD), GivenName (givenName), and Surname (sn). You could try matching against the DisplayName:
Get-ADUser -Filter "DisplayName -eq '$($_.name)'"
If none of those properties match your data exactly, you will have some trouble. No one thing you do will probably work for every account. Hopefully this is just a one-time job and you can work through them in pieces (try one thing, take out the ones that work, and try something different on the rest).
One thing you can try is using AD's Ambiguous Name Resolution (ANR), which will match a search string against several different attributes and even match a first and last name against givenName and sn. That might work with some in your list. You would use it like this:
Get-ADUser -LDAPFilter "(anr=$($_.name))"
If none of that works, you'll have to split the names (for example, by spaces: $_.name.Split(" ")) and try to match pieces of it to different attributes. You'll have to look at your data and see what works.
One approach is to use the Ambiguous Name Resolution feature in Active Directory.
It'll do fuzzy matching against multiple attributes, like the displayName, Name and mail attributes (among others), so it's pretty good for this exact kind of scenario where you don't necessarily know the order or the names or the full name up front:
Get-ADUser -LDAPFilter "(&(anr=$($_.name)))"
I recommend using LDAPFilter and Ambiguous Name Resolution (anr) with Get-ADUser. The algorithm looks up several name fields in different orders to find matches:
Get-ADUser -LDAPFilter "(anr=John Doe)"
Or modifying your code:
Get-ADUser -LDAPFilter "(anr=$($_.name))"
You could try something like the following:
Import-Module ActiveDirectory
$allUsers = Get-Content "\\server\users\folder\Oracle_HR.csv"
$users = #()
ForEach($obj in $allUsers){
$user = Get-ADUser -Filter { GivenName -like $obj} -Properties Name, samAccountName
if(!$user){
$user = Get-ADUser -Filter { SurName -like $obj} -Properties Name, samAccountName
}
if(!$user){
$user = Get-ADUser -Filter { Name -like $obj} -Properties Name, samAccountName
}
if(!$user){
Write-Host "User $obj could not be found" -ForegroundColor Red
}else{
$users += $user
}
}
$users | Select-Object Name,SamAccountName | Export-CSV "\\server\users\folder\Oracle_ADs.csv" -NoTypeInformation
You might need to split the values also like:
Import-Module ActiveDirectory
$allUsers = Get-Content "\\server\users\folder\Oracle_HR.csv"
$users = #()
ForEach($obj in $allUsers){
$objSplit = $obj.Split(",")
foreach($split in $objSplit){
$user = Get-ADUser -Filter { GivenName -like $split} -Properties Name, samAccountName
if(!$user){
$user = Get-ADUser -Filter { SurName -like $split} -Properties Name, samAccountName
}
if(!$user){
$user = Get-ADUser -Filter { Name -like $split} -Properties Name, samAccountName
}
if(!$user){
Write-Host "User $split could not be found" -ForegroundColor Red
}else{
if($users.samAccountName -notcontains $user.SamAccountName){
$users += $user
}
}
}
}
$users | Select-Object Name,SamAccountName | Export-CSV "\\server\users\folder\Oracle_ADs.csv" -NoTypeInformation

Escape apostrophes from variable in powershell

I have the following script set up, and it works as intended....except for users with apostrophes in their email.
$users = get-content C:\Filename.csv
$results = Foreach ($user in $users)
{
get-aduser -filter "EmailAddress -like '$user'" -properties CanonicalName,LastLogon,Name |
select Name,#{N="Thing";E={$_.DistinguishedName.Split(',')[-4]}},#{N="Place";E={$_.DistinguishedName.Split(',')[-5]}},#{N="Last Logon";E `
={[DateTime]::FromFileTime($_.Lastlogon)}}
}
$results | Export-Csv C:Filename.csv -NoTypeInformation
Does anyone know how to escape the apostrophe in an email like example_o'connor#example.com? Whenever I run the above script it always errors out on email addresses like the one above.
Edit: My csv file is a single column of email addresses with no header.
Edit 2: Sorry to waste everyone's time. Turns out the script was working fine, along with some of the suggestions in this thread such as adding "" "" around my variable, but it was that I was testing against a list where not all the values were correct so that is my fault. Thank you everyone for your time.
Try this...
Since you are using a CSV, I imagine you've piped them from another AD cmdlet.
Get-Member is a great tool here.
$users | Get-Member will output TypeName: CSV:Selected.Microsoft.ActiveDirectory.Management.ADUser
However, this is not a String type which the Filter parameter in ADUser needs.
So to convert this...
$selection | Get-Member will output TypeName: System.String.
The other portion is to make sure that the -Filter parameter in Get-ADUser is expanding the strings properly one-by-one, since Get-ADUser's -Filter parameter cannot deal with String Arrays.
$users = Import-Csv C:\filename.csv
$selection = ($users.emailaddress)
foreach ($user in $selection)
{
get-aduser -filter {EmailAddress -eq $user} -properties CanonicalName,LastLogon,Name |`
select Name,#{N="Thing";E={$_.DistinguishedName.Split(',')[-4]}},`
#{N="Place";E={$_.DistinguishedName.Split(',')[-5]}},`
#{N="Last Logon";E={[DateTime]::FromFileTime($_.Lastlogon)}}
}

How do I get Powershell to output Get-ADUser -Filter * as comma delimited?

I have a simple Powershell script that I want to use to grab all the users in AD and show specific properties. Here is the heart of my script:
$id = "*"
Get-ADUser -Filter {SAMAccountName -like $id} -Properties * | Select-Object -Property SAMAccountName,Name,PasswordNeverExpires,LockedOut,PasswordLastSet,LastLogOnDate,CanonicalName
The full script has an input parameter to set $id so that it can be a single ID, a list of IDs (such as "bsmith, jdoe, gmanning"), or * to get all accounts.
Here's my problem: When using, *, I need this to output as comma delimited.
Here's the catch: The output cannot be to a CSV file--or any other type of file.
The reason being is that I'm writing this script to be used on N-Enable's N-Central monitoring suite of software. You don't need to know N-Central to help with my problem, just understand that N-Central uses its own software to run Powershell scripts on clients and returns the results into a txt file that cannot be changed to a csv file (or formatted in any other way than what it has hard-coded).
What this means is that my results have to either be what would show up on the screen or in a variable (such as $results=Get-ADUser -Filter * ....). I cannot output to any type of file, which leaves out Export-CSV as an option.
I've tried other types of formatting to no avail (such as with -f). The issue seems to be with the way Powershell grabs all AD Users using the * wildcard. It seems to grab them all as one big object, so I am unable to get my output to have a comma between all the properties so that I get a comma-delimited output. Thus, when I get the results back from N-Central as a .txt file, all the data is there, but there are no commas in between the properties for me to then open the .txt file as comma-delimited in Excel (or tab-delimited for that matter).
Does anyone have a solution that will allow me to format Get-ADUser -filter * so that it is comma-delimited without using export to file?
UPDATE: Ok, I thought I was keeping things easy by not posting my full script but it seems I've done the opposite. So, below is my full script. Anyone should be able to run this to see the results:
function Get-ADUserInfo
{
[CmdletBinding()]
param(
#[Parameter(Mandatory=$True)]
[string[]]$Users= '*'
)
Begin {
$maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$Headers="ID, Name, Password Never Expires?, Locked Out, Password Last Set, Expiry Date, Last Logon Date, OU Path"
}
Process {
foreach ($id in $Users)
{
$results=Get-ADUser -Filter {SAMAccountName -like $id} -Properties * | select -property SAMAccountName,Name, PasswordNeverExpires,LockedOut,PasswordLastSet,(#{Expression={$_.PasswordLastSet.AddDays($maxPasswordAge)}}), LastLogOnDate,CanonicalName | `
ConvertTo-CSV -NoTypeInformation
}
}
End {
$Headers
$results
}
}
Get-ADUserInfo -Users
Some notes:
When calling Get-AdUserInfo -Users, the script needs to work by entering a single ID, *, or multiple IDs separated by a comma when using the -Users parameter.
Using ConvertTo-CSV solved my biggest problem, comma separated output,thanks all.
I'd like to get rid of the headers that are auto-created as well ("SAMAccountName","Name","PasswordNeverExpires","LockedOut","PasswordLastSet","$_.PasswordLastSet.AddDays($maxPasswordAge)","LastLogOnDate","CanonicalName"). How can I do that? I've tried -skip 1 but that doesn't work with * and removes everything (including the data) if used with a single ID or IDs separated with commas. I can't get -ExpandProperty to work either. Adding format-table -hidetableheaders at the end doesn't do anything as well
So you could use something like this then
$props = "SAMAccountName","Name","PasswordNeverExpires","LockedOut","PasswordLastSet","LastLogOnDate","CanonicalName"
$results = Get-ADUser -Filter * -Properties $props | Select $props | ConvertTo-Csv -NoTypeInformation
This uses the $props array to make the actual query readable and enforce property order. Most of those properties are returned by default, like samaccountname, so it is not required to specify them but no harm done.
$results would contain a quoted comma delimited output. If the quotes bother you they should be removed with a simple -replace
$results = $results -replace '"'
Aside
As others have mentioned you are wasting time using -Properties * you should only return the properties that you need. Which is, again, why I used $props.
Update from comments
If you want to remove the header column you just need to -Skip that from the output. At the end of the code that populates results just add Select-Object
$results = Get-ADUser -Filter * -Properties $props | Select $props | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1
Also to cover your calculated property the easiest way that I have found so far is to add another Select-Object. Not the way I would want to but for now it works.
$results = Get-ADUser -Filter * -Properties $props |
Select $props | Select-Object *,#{L="pWD";Expression={$_.PasswordLastSet.AddDays($masPasswordAge)}} |
ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1
Game Changer
So you made some pretty big changes to your example by including your function. Try this on for size now.
function Get-ADUserInfo
{
[CmdletBinding()]
param(
#[Parameter(Mandatory=$True)]
[string[]]$Users= '*'
)
Begin{
$results = #()
$maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$Headers= #{Label="ID";Expression={$_.SAMAccountName}},
#{Label="Name";Expression={$_.Name}},
#{Label="Password Never Expires?";Expression={$_.PasswordNeverExpires}},
#{Label="Locked Out";Expression={$_.LockedOut}},
#{Label="Password Last Set";Expression={$_.PasswordLastSet}},
#{Label="Expiry Date";Expression={$_.PasswordLastSet.AddDays($maxPasswordAge)}},
#{Label="Last Logon Date";Expression={$_.LastLogOnDate}},
#{Label="OU Path";Expression={$_.CanonicalName}}
}
Process{
foreach ($id in $Users){
$results += Get-ADUser -Filter {SAMAccountName -like $id} -Properties PasswordNeverExpires,LockedOut,PasswordLastSet,LastLogOnDate,CanonicalName | select $Headers
}
}
End{
$results | ConvertTo-CSV -NoTypeInformation
}
}
Get-ADUserInfo -Users
$Headers= contains a collection of calculate properties most of which are there the change the header. It does not need to be done this was and you could have just manually added a header line before the output of contents. This solution however is more in line with what PowerShell is capable of.
We collect all of the users in the array $results and in the End block convert that to CSV output which would not contain your custom headers.
$id = "*"
$userlist = Get-ADUser -Filter {SAMAccountName -like $id} -Properties SAMAccountName,Name,PasswordNeverExpires,LockedOut,PasswordLastSet,LastLogOnDate,CanonicalName | ConvertTo-CSV -NoTypeInformation
$userlist will be an array of users stored in CSV formatted lines
Edit: Combined -Properties * | Select [propertylist] and get rid of the pull of all properties.
Edit: included -NoTypeInformation so that the output is pure CSV.
Note: Something should be done about the filter.