Receive-Job different than direct call - windows

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.

Related

How to format CCM_UserAffinity output to tyoe Microsoft.PowerShell.Commands.LocalPrincipal

I am trying to create a deployment script which adds freshly deployed workstation primary users to local admin group. I utilized CCM_userAffinity class to obtain username, however - Add-LocalGroupMember does not accept its output.
Ive tried creating task sequence variable to pass into powershell script which adds to group with no success either. Preferably the solution would be integrated within deployment TS, however due to no success i have reverted to ps package deployment.
$computer = "LocalHost"
$namespace = "root\ccm\Policy\Machine"
$query = "ConsoleUser"
$PrimaryUser = Get-WmiObject -class CCM_UserAffinity -computername $computer -namespace $namespace | select-object $query | format-wide
i expected the output from -class CCM_UserAffinity to be accepted by Add-LocalGroupMember, however i get this instead -
Add-LocalGroupMember : Cannot bind parameter 'Member'. Cannot convert the "Microsoft.PowerShell.Commands.Internal.Format.FormatStartData" value of type
"Microsoft.PowerShell.Commands.Internal.Format.FormatStartData" to type "Microsoft.PowerShell.Commands.LocalPrincipal".
As you plan on using the value you retrieve and not displaying it there is no need to use something like "format-wide" which only makes the output human readable and is the reason for your FormatStartData datatype.
You can just use :
$PrimaryUser = (Get-WmiObject -class CCM_UserAffinity -computername $computer -namespace $namespace).ConsoleUser
which returns a string and that is taken by the -Member argument of Add-LocalGroupMember
One thing to keep in mind is that in theory there can be more than one ConsoleUser per machine. So the ConsoleUser might be an Array or not. If you can guarantee that there is always only one user in your environment per machine (at the point where the ts runs) you can just use it as is. Otherwise you would have to specify which user you want to use and I can of course not tell you what a good rule for that for your environment would be.
Also I hope you checked that the WMI class CCM_UserAffinity is already populated at the stage where you want to run this script, I could not tell you if this is the case.

Powershell script: List files with specific change date (Amount if possible)

For license porpuses I try to automate the counting process instead of having to login into every single server, go into directory, search a file name and count the results based on the change date.
Want I'm aiming for:
Running a powershell script every month that checks the directory "C:\Users" for the file "Outlook.pst" recursively. And then filters the result by change date (one month or newer). Then packing this into an email to send to my inbox.
I'm not sure if that's possible, cause I am fairly new to powershell. Would appreciate your help!
It is possible.
I dont know how to start a ps session on a remote computer, but I think the cmdlet Enter-PSSession will do the trick. Or at least it was the first result while searching for "open remote powershell session". If that does not work use the Invoke-Command as suggested by lit to get $outlookFiles as suggested below.
For the rest use this.
$outlookFiles = Get-ChildItem -Path "C:\Users" -Recurse | Where-Object { $_.Name -eq "Outlook.pst" }
Now you have all files that have this name. If you are not familiar with the pipe in powershell it redirects all objects it found with the Get-ChildItem to the next pipe section and here the Where-Object will filter the received objects. If the current object ($_) will pass the condition it is returned by the whole command.
Now you can filter these objects again to only include the latest ones with.
$latestDate = (Get-Date).AddMonths(-1)
$newFiles = $outlookFiles | Where-Object { $_.LastAccessTime -gt $latestDate }
Now you have all the data you want in one object. Now you only have to format this how you like it e.g. you could use $mailBody = $newFiles | Out-String and then use Send-MailMessage -To x#y.z -From r#g.b -Body $mailBodyto send the mail.

Fastest way to count ad users in PowerShell?

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!

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()

Setting counter on Windows Event log for Email notification

I am having below script:
$pattern = 'Unable to authenticate user!'
$events = Get-WinEvent -ea SilentlyContinue `
-ProviderName "Windows DB Controller - Task Manager Service"|
Where-Object { $_.TimeCreated -gt [datetime]::today -and $_.Message -match $pattern }
$events >> D:\Error.txt
if ($events) {
Send-MailMessage -SmtpServer smtp.domain.com -From No-reply#domain.com -To sunny#domain.com -Subject 'Error found in log' -Body $events
}
I had scheduled it to run on every 10 mins and purposely ,I wanted to achieve following point using above script:
Search the specified error message in the event viewer log only for current-date and as soon as the error message encountered send a email notification to me but didn't want to receive email notification for the error message which appreared today and for which I had already been notified (I mean , wanted to receive error-notification only once for a specific time of current day).
But problem I am facing here is: Getting multiple notifications for same error message for which already being notified.
I hope I am clear enough to put my exact problem.
Could you please help me out, how to resolve this problem ?
If you are running the script every 10 minutes, I would change the condition on the Where-Object so instead of getting all of the events that are "today"; I would change it to get only the events that happened in the last 10 minutes. i.e. the code becomes:
Where-Object { $_.TimeCreated -gt [datetime]::now.AddMinutes(-10) -and $_.Message -match $pattern }
Have a look at this thread:
Powershell - Tail Windows Event Log? Is it possible?
It's on tailing an event log, but the same method should work for what you're tyring to do. Just save the last index number to a file between runs.
How about the following approach:
Register-WmiEvent -Query "select * from __InstanceCreationEvent where TargetInstance ISA 'Win32_NTLogEvent' and TargetInstance.SourceName = 'Windows DB Controller - Task Manager Service' and TargetInstance.Message LIKE '%Unable to authenticate user!%'" -SourceIdentifier "MyEventListener" -Action {
#Your actions
write-host "$($eventargs.NewEvent.TargetInstance.RecordNumber) at $($eventargs.NewEvent.TargetInstance.TimeGenerated)"
}
It uses WMI to subscribe to the event that occurs when an eventlog entry is generated with your criterias. The action itself will only return the new object(so no more duplicates). I've included a sample action to help you understand how to access the object. This method will give you live monitoring.
Inside the action, $eventargs.NewEvent.TargetInstance will give you the object which is an instance of win32_ntlogevent. To see properties of this class, check out TechNet or run the following command:
([wmiclass]'win32_ntlogevent').Properties | ft Name
To make the script run forever, just call your script with powershell -file script.ps1 -noexit or include a while($true) loop at the end of your script. (I'm not sure how the while-loop will affect resource usage longterm, you'd have to test).

Resources