Using Quest Powershell to gather info and report - powershell

I need some help writing a script as i am struggling to understand the logic.
I basically have a list of user ids that i need to check to see if they have two certain AD groups. If they have, these need to be outputted into a csv and highlighted.
Can anyone help to get me started? I need to use the Quest Powershell cmdlets
Here is the code
$textFileContents = Get-Content C:\temp\powershell\users.txt
$results = #()
foreach($username in $textFileContents){
$groups = get-qaduser $username |select -expand memberof
if ($groups -match "grpuip1" -and $groups -match "group2"){
echo $group
}
}

check this to begin :
"user1","user2" | foreach {
$groups = get-qaduser $_ |select -expand memberof
if ($groups -match "GROUP1" -and $groups -match "GROUP2"){
echo $_
}
}

I'd use the cmdlet Get-QADMemberOf instead of Get-QADUser. There's nothing wrong with what you're doing, but it's retrieving more information than you need.
Try this to start with:
$textFileContents = Get-Content C:\temp\powershell\users.txt
# Rather than initializing the array, and adding new elements,
# just output each element of the loop to the pipeline, and
# assign the results of the whole pipeline to the variable.
# This is *much* faster than adding to an array
$results = $textFileContents | ForEach-Object {
$userGroups = Get-QADMemberOf $_
if ($userGroups -contains "group1" -and $userGroups -contains "group2") {
New-Object -TypeName PSObject -Property #{"UserName" = $_; "Groups" = ($userGroups -join ",");}
}
}
$results | ConvertTo-Csv -NoTypeInformation | Set-Content C:\Filename.txt

Related

Create csv file of all disabled AD users with mailboxes Output information from multiple cmdlets in powershell

I am trying to gather some information on disabled user accounts that have mailboxes. I am specifically looking for just user mailboxes not shared mailboxes.
Here is what I have so far.
$Mailboxes = Get-Mailbox | where {$_.RecipientTypeDetails -eq 'UserMailbox'}
$date = get-date -f "MMddyyyy_HHmm"
$Disabled = #()
Foreach ($Mailbox in $Mailboxes) {
if((Get-ADUser -Identity $Mailbox.SamAccountName).Enabled -eq $False){
$Disabled += Get-MailboxStatistics $Mailbox.SamAccountName | Select -Property DisplayName,TotalItemSize
}
}
$Disabled | Sort DisplayName | Export-Csv -Path "%path%\DisabledADUsersWithMailbox_$date`.csv" -NoTypeInformation
Additionally what I would like to collect is the users Title, Manager, LastlogonDate all of which can be found using Get-Aduser. I am unsure how I go about collecting the information from both cmdlets and then exporting it all to csv. I have read that I may need to create a custom object. I am struggling with setting that up in this script.
Any help would be much appreciated.
Thanks
the following lines should give you what you want, can't verify it as I have no exchange running here.
$date = get-date -f "MMddyyyy_HHmm"
$Disabled = #(
Foreach ($Mailbox in $Mailboxes) {
$adUser = get-aduser -Identity $Mailbox.SamAccountName -Properties enabled,manager,title,lastlogontimestamp
If ($adUser.Enabled -eq $False){
$mailStats = Get-MailboxStatistics $Mailbox.SamAccountName
$attrsht = [ordered]#{
displayname=$mailstats.displayname
totalitemsize=$mailStats.totalitemsize
samaccountname=$aduser.samaccountname
enabled=$aduser.enabled
manager=$aduser.manager
title=$aduser.title
lastlogontimestamp=[datetime]::FromFileTime($aduser.lastlogontimestamp)
}
new-object -TypeName psobject -Property $attrsht
}
}
)
$Disabled | Sort-Object DisplayName | Export-Csv -Path "%path%\DisabledADUsersWithMailbox_$date`.csv" -NoTypeInformation
Avoid adding elements to an array by using +=. It is slow, alternatively take a look at generic array lists.

Reducing memory consumption of PowerShell script

I know this is pulling quite a bit of data, but at present it's capping my memory consumption when I run it on my local machine. The good news is, it's returning the output that I need. Can someone help me with performance optimization? So far, I haven't done much for fear of messing up a script that returns my desired output. Thanks in advance for any suggestions.
#// Start of script
#// Get year and month for csv export file
$DateTime = Get-Date -f "yyyy-MM"
#// Set CSV file name
$CSVFile = "C:\Temp\AD_Groups"+$DateTime+".csv"
#// Create emy array for CSV data
$CSVOutput = #()
Measure-Command {
#// Get all AD groups in the domain
$ADGroups = Get-ADGroup -Filter "GroupScope -ne 'DomainLocal' -AND GroupCategory -eq 'Security' -AND Member -like '*'" -SearchBase "OU=SHS, DC=shs, DC=net" -Properties Member #-ResultSetSize 1000 Name -like '*''s*' -AND
#// Set progress bar variables
$i=0
$tot = $ADGroups.count
foreach ($ADGroup in $ADGroups) {
#// Set up progress bar
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot * 100)
#// Ensure Members variable is empty
$Members = ""
#// Get group members which are also groups and add to string
$MembersArr = Get-ADGroup $ADGroup.DistinguishedName -Properties Member | Select-Object -ExpandProperty Member
if ($MembersArr) {
foreach ($Member in $MembersArr) {
$ADObj = Get-ADObject -filter {DistinguishedName -eq $Member}
#// Initialize regex variable
$matches = ""
if ($ADObj.ObjectClass -eq "user") {
$UserObj = Get-ADObject -filter {DistinguishedName -eq $Member}
$match = $UserObj -match '\([a-zA-Z0-9]+\)'
$empid=$matches[0] -replace ".*\(","" -replace "\)",""
if ($UserObj.Enabled -eq $False) {
continue
}
$Members = $empid
}
# Check for null members to avoid error for empty groups
if ([string]::IsNullOrEmpty($Members)) {
continue
}
$HashTab = [ordered]#{
"GroupName" = $ADGroup.Name -replace "'s", "''s"
"GroupCategory" = $ADGroup.GroupCategory
"GroupScope" = $ADGroup.GroupScope
"MemberID" = if([string]::IsNullOrEmpty($empid)){""}
else{$empid}
}
#// Add hash table to CSV data array
$CSVOutput += New-Object PSObject -Property $HashTab
}
}
#// Export to CSV files
$CSVOutput | Sort-Object Name, Member | Export-Csv $CSVFile -NoTypeInformation
}
}
I've experienced this too with code that loops through thousands of accounts. The problem is that the garbage collector doesn't have time during the loop to clean up, since your code is constantly doing something. In .NET, I'd call .Dispose() manually to make sure stuff is cleaned up, but here you can't.
You can try calling [System.GC]::Collect() after you assign each variable in the loop. For example, after $MembersArr = and after $ADObj = to (hopefully) make it deallocate the memory used for the previous value.
Also, I think that $UserObj = Get-ADObject... line should be calling Get-ADUser, not Get-ADObject. As it is, $UserObj.Enabled will never have a value and your continue will never be hit.
But you can save yourself the use of Get-ADUser entirely by asking for the userAccountControl value in Get-ADObject and using that to determine if the user is disabled. For example:
$ADObj = Get-ADObject -filter {DistinguishedName -eq $Member} -Properties userAccountControl
# Clean up the old $ADObj value
[System.GC]::Collect()
#// Initialize regex variable
$matches = ""
if ($ADObj.ObjectClass -eq "user") {
$match = $ADObj -match '\([a-zA-Z0-9]+\)'
$empid=$matches[0] -replace ".*\(","" -replace "\)",""
if ($ADObj.userAccountControl -band 2) {
continue
}
$Members = $empid
}
The $ADObj.userAccountControl -band 2 condition checks is a bitwise AND comparison to check if the second bit of the userAccountControl value is set, which means that the account is disabled.

Can I use splatting within a Where-Object using PowerShell?

What I want to find out if I can do, is splatting in a Where-Object clause or something similar. I know you can do this with parameters already.
I am trying to filter out multiple values from one property using -notlike.
I have looked at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-5.1 but nothing is mentioned in here.
An example of what I'm currently trying to do:
$allUsers | Where-Object {($_.UserPrincipalName -notlike "Health*") -and ($_.UserPrincipalName -notlike "admin*")}
I am trying to do it this way as there are lots of accounts I want to exclude that contain the the word "admin" in their UPN. Unfortunately there is a long list of what I need to exclude, as I am running through a cleanup process.
I have been suggested to use an array of exclusions and then try using -notcontains or -notin but this has not worked for me, I'm assuming because I need it to be wildcard friendly.
I think it is a lot easier to use the regex -notmatch operator:
# create a regular expression string by combining the keywords with the OR '|' character
$exclude = ('test','admin','health' | ForEach-Object { [regex]::Escape($_) }) -join '|'
# get all users except the users that have any of the keywords in their UserPrincipalName
Get-ADUser -Filter * | Where-Object { $_.UserPrincipalName -notmatch $exclude }
You can do the following to build a dynamic filter by just comma-separating your exclusion strings, which effectively creates an array of strings ($Exclusions).
$Exclusions = 'Health*','admin*'
$FilterArray = $Exclusions | Foreach-Object {
"UserPrincipalName -notlike '$_'"
}
$Filter = #{'Filter' = "{0}" -f ($FilterArray -join " -and ")}
Get-ADUser #Filter
-notin works fine for me, maybe supply a bit more of your data in your question to see what you;re trying to filter out? Is it the wildcards causing an issue?
$listofexclusions = #("userid1","userid2")
Get-ADUser -Filter * | Where-Object { $_.SamAccountName -notin $listofexclusions } | Select Name,SamAccountName | ft
Edit:
If you need wildcards, there are some previous threads about this here
If you're using something like Get-ADUser you can do what other answers have suggested and let the cmdlet filter the results for you using its built in functionality.
However, if you just want to apply a bunch of -like operations to a collection of objects you can write your own Test-LikeAny function as follows:
function Test-LikeAny
{
param( [string] $value, [string[]] $patterns )
foreach( $pattern in $patterns )
{
if( $value -like $pattern )
{
return $true
}
}
return $false
}
and then you can use it like this:
$values = #(
(new-object pscustomobject #{ "Property1" = "...value1..." }),
(new-object pscustomobject #{ "Property1" = "...value2..." }),
(new-object pscustomobject #{ "Property1" = "...value3..." })
)
$patterns = #( "*value1*", "*value2*" )
$values | Where-Object { -not (Test-LikeAny $_.Property1 $patterns) }
which gives:
Name Value
---- -----
Property1 ...value3...

How to get list of selected AD Groups, that a large list of users are members of?

I have the below working script that checks if a large list of users in a CSV file are a member of an AD group and writes the results to results.csv.
Not sure how to convert the script so I can change $group = "InfraLite" to $group = DC .\List_Of_AD_Groups.CSV.
So the script doesn't just return matches for one AD group but so it returns matches for the 80 AD groups contained in the List_of_AD_groups.csv also. Writing a YES/NO for each AD group in a new column in the CSV (or if that's not possible creating a seperate .csv file for each group with results would do also.
I could do this manually by changing the value of $group and export file name, and re-running the script 80 times but must be a quick was with PS to do this?
e.g. results.csv:
NAME AD_GROUP1 AD_GROUP2 AD_GROUP80 etc etc.
user1 yes no yes
user2 no no yes
user3 no yes no
echo "UserName`InfraLite" >> results.csv
$users = GC .\user_list.csv
$group = "InfraLite"
$members = Get-ADGroupMember -Identity $group -Recursive |
Select -ExpandProperty SAMAccountName
foreach ($user in $users) {
if ($members -contains $user) {
echo "$user $group`tYes" >> results.csv
} else {
echo "$user`tNo" >> results.csv
}
}
I played with this for a while, and I think I found a way to get you exactly what you were after.
I think Ansgar was on the right path, but I couldn't quite get it to do what you were after. He mentioned that he didn't access to an AD environment at the time of writing.
Here is what I came up with:
$UserArray = Get-Content 'C:\Temp\Users.txt'
$GroupArray = Get-Content 'C:\Temp\Groups.txt'
$OutputFile = 'C:\Temp\Something.csv'
# Setting up a hashtable for later use
$UserHash = New-Object -TypeName System.Collections.Hashtable
# Outer loop to add users and membership to UserHash
$UserArray | ForEach-Object{
$UserInfo = Get-ADUser $_ -Properties MemberOf
# Strips the LPAP syntax to just the SAMAccountName of the group
$Memberships = $UserInfo.MemberOf | ForEach-Object{
($_.Split(',')[0]).replace('CN=','')
}
#Adding the User=Membership pair to the Hash
$UserHash.Add($_,$Memberships)
}
# Outer loop to create an object per user
$Results = $UserArray | ForEach-Object{
# First create a simple object
$User = New-Object -TypeName PSCustomObject -Property #{
Name = $_
}
# Dynamically add members to the object, based on the $GroupArray
$GroupArray | ForEach-Object {
#Checking $UserHash to see if group shows up in user's membership list
$UserIsMember = $UserHash.($User.Name) -contains $_
#Adding property to object, and value
$User | Add-Member -MemberType NoteProperty -Name $_ -Value $UserIsMember
}
#Returning the object to the variable
Return $User
}
#Convert the objects to a CSV, then output them
$Results | ConvertTo-CSV -NoTypeInformation | Out-File $OutputFile
Hopefully that all makes sense. I commented as much of it as I could. It would be very simple to convert to using ADSI if you didn't have RSAT installed on whatever machine you're running this on. If you need that let me know, and I'll make some quick modifications.
I've also tossed a slightly modified version of this in a Gist for later reference.
The trivial solution to your problem would be to wrap your existing code in another loop and create an output file for each group:
$groups = Get-Content 'C:\groups.txt'
foreach ($group in $groups) {
$members = Get-ADGroupMember ...
...
}
A more elegant approach would be to create a group mapping template, clone it for each user, and fill the copy with the user's group memberships. Something like this should work:
$template = #{}
Get-Content 'C:\groups.txt' | ForEach-Object {
$template[$_] = $false
}
$groups = #{}
Get-ADGroup -Filter * | ForEach-Object {
$groups[$_.DistinguishedName] = $_.Name
}
Get-ADUser -Filter * -Properties MemberOf | ForEach-Object {
$groupmap = $template.Clone()
$_.MemberOf |
ForEach-Object { $groups[$_] } |
Where-Object { $groupmap.ContainsKey($_) } |
ForEach-Object { $groupmap[$_] = $true }
New-Object -Type PSObject -Property $groupmap
} | Export-Csv 'C:\user_group_mapping.csv' -NoType

Trying to rename several account types at once based on current displayName

This morning some awesome people helped me make a script to move user accounts based on their displayName to a certain OU. I tested and it worked. I cannibalized the script to make another one that will rename the same accounts based off of the same criteria. I've gone through several errors but basically it all boils down to "I am having an identity crisis!". I can't seem to figure out exactly what I need to input as the $Identity. Here is what I have:
Import-Module ActiveDirectory
$Renames = #(
#{
Filter = 'DisplayName -like "*Supply*"'
NewName = "Supplies"
},
#{
Filter = 'DisplayName -like "*Accountant*"'
NewName = "Accounting"
}
) | ForEach-Object {New-Object -TypeName PSCustomObject -Property $_}
$OriginOU = "OU=Test,OU=Standard Users,OU=Domain Users,DC=com"
foreach ($Rename in $Renames) {
Get-ADUser -SearchBase $OriginOU -Filter $Rename.Filter -Properties displayName |
Where-Object {($_.Enabled -eq 'True') -and ($_.DistinguishedName -notlike '*DontTouch*')} |
%{Set-ADUser $_ -DisplayName {$_.DisplayName -replace '(.EPSILON ).+',"`$1$Rename.NewName"}}
}
You can't use the current object variable ($_) if you have Set-ADUser read directly from the pipeline. And since Set-ADUser apparently doesn't play nice with scriptblock arguments, you have to put the statement in a loop:
... | % { Set-ADUser $_ -DisplayName ($_.DisplayName -replace '(.EPSILON ).+',"`$1$($Rename.NewName)") }
Note that if you want to expand object properties inside a string you have to put $Rename.NewName in a subexpression ($()), otherwise the whole object $Rename would be stringified and the string ".NewName" would be appended to it.