Fastest way to count ad users in PowerShell? - performance

I've been looking for the fastest way to count the number of ad users in powershell, aswell as the number of enabled and disabled users, but the queries are very long (~100k users, 1-2 min per query) and my powershell ISE usually crashes after one or two requests (it's for a reporting job)
So my question is how can I optimize these queries :
$CountADUsers = (get-aduser –filter * -server $myserver).count.Count
$CountADUsersEnabled = (get-aduser -filter * -server $myserver | where {$_.enabled -eq "True"}).count
$CountADUsersNotEnabled = $CountADUsers - $CountADUsersEnabled
Thanks in advance guys

You don't need to run Get-AdUser twice. You can save it to variable and just filter it:
$allUsers = get-aduser –filter * -server $myserver
$CountADUsers = $allUsers.count
$CountADUsersEnabled = ($allUsers | where {$_.enabled -eq "True"}).count
Also, it won't be helpful in that case, but please remember that using -Filter * and then Where-Object is not very effective as you can just use:
Get-ADUser -Filter {enabled -eq "True"}
Another hint: ISE shouldn't be used for running scripts as it sometimes behave in strange ways (especially when you run out of memory on your machine). You should use powershell.exe instead.
Edit: to improve even more you could try to choose only the attributes you need using
$t = $allusers |select userprincipalname,enabled
And then use Where-Object to filter. For comparison:
Measure-command {($allusers | where {$_.enabled -eq "True"}).count}
took two minutes when
Measure-command {($t | where {$_.enabled -eq "True"}).count}
took two seconds (but selecting takes about 2 mins so the overall time is more or less the same). However, this strongly depends on the scenario. I'll leave it to you so you can find the best solution for your case. Remember, Measure-Command is your very good friend for this!

Related

Powershell script CPU usage high

I've got a simple script that looks for a file across a bunch of servers. Issue is that on each server it is pegging CPU and causing issues for production workloads. How can I get this script to not DDoS my machines?!
Start-transcript C:\tools\Querylog.txt
$Servers = Get-ADComputer -Filter {(OperatingSystem -like "*windows*server*") -and (Enabled -eq "True")} -Properties OperatingSystem
Invoke-command -ComputerName $Server-ScriptBlock {Get-ChildItem -Path $_.Root -Recurse -Force -File -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*somefile-readme*' } |Out-File -FilePath <filePath>\results.txt -Append}
Stop-Transcript
Use the -Filter option with Get-ChildItem, it should be much more performant than returning all objects and filtering with Where-Object.
Also, not related to your CPU issue but in how you are crafting your Get-ADComputer call, you should use a String, not a ScriptBlock for the -Filter arguments on these cmdlets. From that answer:
-Filter doesn't support ScriptBlocks, but they Kind of Work Sometimes™ because they get ToString'd before getting evaluated. They will technically work if you use simple variable expansion like $_ or $emailAddress, but it will eventually cause you a headache, especially if you try to access an object property (like above) because it simply won't work. Use a string filter every time, and if you need to use an object property as a filter parameter, use Variable Substitution or Command Substitution.

Detection script for windows hotfixes

So we have recently had issues with the KB971033 update within our network and i have managed to get a working script for removing it and reactivating windows, however when trying to get a detection script working to assure it only runs on effected computers i cant get it to correctly output true or false when testing against installed KBs.
So far this is what im running. No matter what i do it will output false. Anything obvious i am missing?
if ((get-hotfix).hotfixid -eq "KB971033") {$true} else {$false}
(get-hotfix).hotfixid returns an array, so you should not compare that with -eq.
This ought to do it:
((Get-HotFix | Select-Object -ExpandProperty HotFixID) -contains 'KB971033')
or for short:
(((Get-HotFix).HotFixID) -contains 'KB971033')
It's IMO quite inefficient to sieve through all Hotfixes when testing a distinct one.
if (Get-Hotfix -ID KB971033 -EA 0) {$true} else {$false}
-EA 0 is an abbreviation for -ErrorAction SilentlyContinue
Maybe Try
if ($(get-hotfix).hotfixid -eq "KB971033") {$true} else {$false}
The "$" is going to make "Get-Hotfix" result into an object with member ".hotfixID".
In my Windows Server 2016 Environment your Code works fine...maybe the Hotfix is not installed or not listed with 'get-hotfix'
Otherwise you can try this:
$HotfixID= "KB971033"
IF((get-hotfix).hotfixid | ?{ $_ -eq $HotfixID}){$true} else {$false}
It works also on Remote Computer:
(get-hotfix).hotfixid -ComputerName "***SomeDNSName / FQDN***"

Receive-Job different than direct call

Needed to change my code from direct call to Start-Job precedure because of timeouts caused by Symantec Antivirus (SEP) v14, making my script hanging.
this snap was working fine so long:
$updateDaten = Get-HotFix -computername "myserver" | % { $_.psbase.properties["installedOn"].Value } | Group-Object | select-object Name
The change to a start-job preocedure completely messes up the resulting object. im googling and testing for two days now but cannot find out what's so complicatet in dealing with job objects:
function Get-HotfixesListAsJob($computer, $timeout){
$job = Start-Job { param($c) Get-HotFix -computername $c |
% { $_.psbase.properties["installedOn"].Value } | Group-Object
} -ArgumentList $computer
Wait-Job $job -Timeout $timeout
Stop-Job $job
Receive-Job $job
Remove-Job $job
}
$updateDaten = Get-HotfixesListAsJob -computer "myserver" -timeout 80
However I am not able to get the same result back using Receive-Job. I Always get some wired job-object and I am not able to just extract the data as it was before. Why is the result so completely different? is it possible to get just the data back in an object as it was before instead of a job object?
btw. this interesting article did not solve my problem:
https://learn-powershell.net/2014/06/27/quick-hits-did-i-really-lose-my-output-with-receive-job-by-not-usingkeep/
Thanks for your answers in advance
The issue what you are having is not because of the code. Its because Start-job or any PS Job session, basically, will have all the details, properties related to that job.
So, I can see in the first case you have done a grouping and finally selecting only the name which is not there in the later case.
So I would suggest you to add that to the Function also.
One more thing , Wait-job will also output the value in the console. Either redirect or pipe it to null in order to avoid that.
You can also receive the job using receive-job with the job id or the name. I think that will be better. Just check the status of the job; Once done receive it.

WMIMethodException with .InstallProductKey

First off, this is my first post, so if I incorrectly posted this in the wrong location, please let me know.
So, what we're trying to accomplish is building a powershell script that we can throw on our workstation image so that once our Windows 10 boxes are done imaging, that we can click on a powershell script, have it pull the key from the BIOS, and automagically activate it. That being said, here is the script that we've put together from various sources.
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
$computer = gc env:computername
$key = get-content c:\license.txt
$service = get-wmiObject -query “select * from SoftwareLicensingService” -computername $computer
$service.InstallProductKey($key) <--------THIS IS WHERE IT FAILS
$service.RefreshLicenseStatus()
We start running into the issues on the line $service.InstallProductKey($key). It seems, that no matter how we try to invoke that, it will consistently fail with the error "Exception calling "InstallProductKey"". I've even replaced the variable ($key) with the specific activation key, and it STILL fails with the same error.
The reason we have it outputting to a license txt file part way through is so that we can verify that the command is indeed pulling the product key (which it is).
At this point, I'm not sure where to go. It seems that people have tried to do this before, however, nobody has really wrapped up their posting with what worked and/or what didn't. I can't imagine that this is impossible, but I'm also not fond of wasting anymore time than needed, so anybody that has any insight into this issue, I'd be very grateful.
We've gotten it to work on two machines that were previously activated, and later deactivated, but on new machines that have been freshly imaged, and have yet to be activated, it will fail every time.
Two things as per my observation:
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
I don't think that it is returning any value to your license.txt.
If yes, then I would like you to see if there is any space before and after the license key. You can use trim during getting the content from the file.
Second thing, when you are getting the content from the file make sure it is not separating into multiple lines. In that case, you have to cast it as string like [String]$key or you can call toString() method for this.
One more important thing is to refresh after the installation.
$service.RefreshLicenseStatus()
Note: Make sure you are running the shell in elevated mode.
Alternative: Try Hardcoding the values and see the result
$key = "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" # hardcode the key
$computer= "Computer01" # Hardcode the computer
$service = get-wmiObject -query "select * from SoftwareLicensingService" -computername $computer
$service.InstallProductKey($key)
$service.RefreshLicenseStatus()
For further thing ,please post the exact error.
Hope it helps...!!!
Found out that the key from Get-WmiObject has whitespace on the end. The original command will work if a .Trim() is added. Also not running as administrator will result in the same error.
(Get-WmiObject -query ‘select * from SoftwareLicensingService’).OA3xOriginalProductKey | out-file c:\license.txt
$computer = gc env:computername
$key = (get-content c:\license.txt).Trim() #trim here
$service = get-wmiObject -query “select * from SoftwareLicensingService” -computername $computer
$service.InstallProductKey($key)
$service.RefreshLicenseStatus()

Is there a way to check AD group membership for a computer?

I am trying to check computer group membership through Powershell. I want to be able to specify a certain computer name and find which groups that computer is in but from a Powershell script. I am planning on running the script on a computer, grabbing the hostname, and then printing out what AD groups that computer is in. Is there an easy way to do this?
EDIT:
So the plan here is to have a computer check to see what groups it is in, then assign a printer based on which group it is in. We have many printers that only 3 to 4 people use but due to the spread out nature of the users cannot downsize the amount of printers. I was looking at group policy but did not want to create 20 different GPOs. I wanted to do this with a logon/startup script. I'm not sure if this is doable or feasible.
Edit #2:
This edit it really late to the party but I figured if anyone found this it could help. We ended up using item level targeting on User>Preferences>Control Panel>Printers objects. We made an account in AD for each group of users needing access to the printers. This worked although it did create a long logon process for the first logon of the computer. We also enabled Point-to-Print restrictions so the drivers were loaded from the servers quietly.
This will give you the group membership (group names) of the local computer (requires powershell 2.0):
([adsisearcher]"(&(objectCategory=computer)(cn=$env:COMPUTERNAME))").FindOne().Properties.memberof -replace '^CN=([^,]+).+$','$1'
Apologies if I'm a bit late to the party on this but I needed to find a computer's group membership as well. After a lot of trial and error, this worked for me.
Get-ADComputer "mycomp" -Properties MemberOf | %{if ($_.MemberOf -like "*group name*") {Write-Host "found"} }
I noticed that if the string comparison is on a separate line, I needed to do the following
$g=Get-ADGroupMember -Identity "CN=myADgroup,OU=myOU,DC=corp,DC=com" -server corp.com
foreach($mbr in $g) {if($name.MemberOf -like "*mycomp*" -eq $true) {Write-Host "found"}}
Not sure but I imagine that testing the computer might be faster and easier than testing the group depending on the number of members.
the gpresult method seems to be the only way I can find that is accurate. Querying AD is not accurate since the computer may have been added to groups but not rebooted. I am sure someone could condense this but it worked well enough for what I needed to see.
# Get all active domain servers
$staledate = (Get-Date).AddDays(-90)
$computers = Get-ADComputer -Filter {(OperatingSystem -Like "*Server*") -and (Enabled -eq $True) -and (LastLogonDate -ge $staledate) -and (Modified -ge $staledate) -and (PasswordLastSet -ge $staledate) -and (whenChanged -ge $staledate) -and (Name -notlike "PHXVSSA101A")} | Select name -expandproperty name
$computers = $computers | Where {(Resolve-DNSName $_.name -ea 0) -and (Test-Connection -ComputerName $_.Name -Count 1 -ea 0)} | Sort
# Loop through all active domain servers
Foreach ($computer in $computers)
{
# Pull the gpresult for the current server
$Lines = gpresult /s $computer /v /SCOPE COMPUTER
# Initialize arrays
$cgroups = #()
$dgroups = #()
# Out equals false by default
$Out = $False
# Define start and end lines for the section we want
$start = "The computer is a part of the following security groups"
$end = "Resultant Set Of Policies for Computer"
# Loop through the gpresult output looking for the computer security group section
ForEach ($Line In $Lines)
{
If ($Line -match $start) {$Out = $True}
If ($Out -eq $True) {$cgroups += $Line}
If ($Line -match $end) {Break}
}
# Loop through all the gathered groups and check for Active Directory groups
ForEach ($group in $cgroups)
{
Try {
$check = Get-ADgroup $group -ErrorAction Stop
If ($check) {
$dgroups += $group
}
}
Catch {}
}
# Output server name and any Active Directory groups it is in
$computer
$dgroups
# End of computers loop
}
try running gpresult /V and check under "security GROUPS"
You can also try gpresult /scope computer /v under a command prompt (elevated to admin) for more specific results
Heres an LDAP query to find if a computer is in a group recursively:
(((objectClass=computer)(sAMAccountName=COMPUTERNAME$))(memberof:1.2.840.113556.1.4.1941:=DistinguishedNameOfGroup))
More info: http://justanotheritblog.co.uk/2016/01/27/recursively-check-if-a-usercomputer-is-a-member-of-an-ad-group-with-powershell-2-0/
To check the computer's own view of group membership, you can run:
(New-Object System.Security.Principal.WindowsPrincipal("$env:computername$")).IsInRole('Example Group')
True
Taking the computer out of Example Group doesn't affect the output of the above until the computer is rebooted.
Try this DOS Command, this will return all the local groups this computer belong to :
net localgroup

Resources