Getting output from a Start-Job invocation - windows

I have the following code that works in that it creates multiple jobs and runs what's inside the scriptblock on all of the computers in the array ($SMSMembers). The problem is that it doesn't give any sort of meaningful output back that I can use to determine if the code ran successfully or not. I have tried about 100 different things that I have Googled but none of the solutions seemed to work for me. This is the code I'm trying to run that I thought should work according to other posts I've seen on StackOverflow.
$SMSMembers = #("computer1","computer2","computer3")
$output = #()
foreach ($compName in $SMSMembers) {
$scriptblock = {
$file = {"test"}
$filepath = "\\$using:compName\c$\scripts\NEWFILE.txt"
$file | Out-File -FilePath $filepath
Start-Sleep -Seconds 5
Remove-Item $filepath
}
$output += Start-Job -ScriptBlock $scriptblock | Get-Job | Receive-Job
}
Get-Job | Wait-Job
foreach ($item in $output) {
Write-Host $item
}
This script doesn't do much except copy a file to a remote computer and then delete it. I would just like to get output if the job was successful or not. Like I said this code works like it should, I just don't get feedback.
My end goal is to be able to send a command to an array of computers ($SMSMembers) and request the current user with this code and get the username input back:
$user = gwmi Win32_ComputerSystem -Comp $compName |
select Username -ExpandProperty Username

You create the job, get the job info, and then receive the job back to back to back, before the job can complete. Instead, collect the job info, then outside the loop wait for the jobs to finish, and receive the output when the jobs are done.
$SMSMembers = #("computer1","computer2","computer3")
$scriptblock = {
$file = {"test"}
$filepath = "\\$using:compName\c$\scripts\NEWFILE.txt"
$file | out-file -FilePath $filepath
Start-Sleep -Seconds 5
remove-item $filepath
}
$Jobs = foreach($compName in $SMSMembers){
Start-Job -ScriptBlock $scriptblock
}
Wait-Job $Jobs
$Output = Receive-Job $Jobs
foreach ($item in $output){
write-host $item
}
Edit: Modified the script slightly so I wasn't randomly copying files around, but it should still function the same. Then tested it with the expected results:
$SMSMembers = #("computer1","computer2","computer3")
$scriptblock = {
$RndDly=Get-Random -Minimum 10 -Maximum 45
start-sleep -Seconds $RndDly
"Slept $RndDly, then completed for $using:compname"
}
$Jobs = foreach($compName in $SMSMembers){
Start-Job -ScriptBlock $scriptblock
}
Wait-Job $Jobs
$Output = Receive-Job $Jobs
foreach ($item in $output){
write-host $item
}
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 BackgroundJob Completed True localhost ...
3 Job3 BackgroundJob Completed True localhost ...
5 Job5 BackgroundJob Completed True localhost ...
Slept 30, then completed for computer1
Slept 27, then completed for computer2
Slept 11, then completed for computer3

Look at the below code and i think you should be able to figure this out.
> Get-job | Receive-Job 2>&1 >> c:\output.log
The 2>&1 >> with collect all output from Get-Job | Receive-Job it should work similarly for start-job

Related

Kill the process if start time is less than 2 hours

I need to kill the process if start time is less than 2 hours.
I have written the below cmdlet to find out the starttime but how to find out if is it less than 2 hours:
get-process process1 | select starttime
There is also a possibility that on some hosts process1 is not running. so I need to check first if the process1 is running
You can use a loop of your choice, in this example ForEach-Object in addition to an if condition to check if the StartTime value is lower than 2 hours.
If you need to check first is the process is running then you would need to get all processes and filter by the process name you're looking for. Then check if the returned value from Where-Object is $null or not.
$procName = 'myprocess'
$process = Get-Process | Where-Object Name -EQ $procName
if(-not $process) {
Write-Warning "$procName not found!"
}
else {
$process | ForEach-Object {
if($_.StartTime -lt [datetime]::Now.AddHours(-2)) {
try {
'Attempting to stop {0}' -f $_.Name
Stop-Process $_ -Force
'{0} successfully stopped.' -f $_.Name
}
catch {
Write-Warning $_.Exception.Message
}
}
}
}

How to splat to a function in powershell?

I have a function (which started out as a ScriptBlock) and I'd like to be able to splat my input parameters to store configurations, but instead I'm passing each of them individually and this is bad, because I'd rather just pass the hashmap when I want to run the script block using Invoke-Command -Command ${function:sb} -ArgumentList $service_host_download_11_1_1
or
Invoke-Command -Command ${function:sb} -ArgumentList $service_host_11_1_1
But thus far I have been unable to achieve this; see the code below, is there a way to do this?
# I'd like to be able to splat these to my sb function
$service_host_download_11_1_1 = #{
packageName = 'ServiceHostDownload'
searchPattern = 'VERSION,'
host_arr = $(Get-ADComputer -LDAPFilter '(&(name=MCDONALDS*)(!(name=MCDONALDSTAB*)))' -SearchBase "OU=Simphony,OU=MCDONALDS,OU=Florda,OU=Places,DC=mckeedees,DC=com" | Select-Object -ExpandProperty Name)
}
# And this also
$service_host_11_1_1 = #{
packageName = 'ServiceHost'
searchPattern = 'VERSION,|REBOOT'
host_arr = $(Get-ADComputer -LDAPFilter '(&(name=MCDONALDS*)(!(name=MCDONALDSTAB*)))' -SearchBase "OU=Simphony,OU=MCDONALDS,OU=Florda,OU=Places,DC=mckeedees,DC=com" | Select-Object -ExpandProperty Name)
}
function sb($host_arr,
$packageName,
$searchPattern) {
$host_arr | % {
Invoke-Command -ComputerName $_ -AsJob -ArgumentList #($packageName, $searchPattern) -ScriptBlock {param($pn, $sp)
$result = gc -path "C:\Micros\Simphony\CALTemp\Packages\$($pn)\Setup_log.txt" | Select-String -Pattern $sp
Write-Host "$($ENV:COMPUTERNAME),$($pn),$($result)"
}
}
}
# I'd rather be splitting with Invoke-Command here.
sb $service_host_download_11_1_1['host_arr'] $service_host_download_11_1_1['packageName'] $service_host_download_11_1_1['searchPattern']
# Wait for completion
Start-Sleep -Seconds 5000
Get-Job | ? { $_.State -eq 'Completed'} | Receive-Job
# Write out failed
#Get-Job | ? { $_.State -eq 'Failed'} | Receive-Job
# Set us up for next time...
Get-Job | Remove-Job

Parallel retrieval of user sessions from multiple Windows servers in PowerShell

Assume I have an array of Windows hostnames:
$server_hostnames = #("server1","server2")
For each server I need to find if there are any sessions for a specific user, and then ideally capture all the sessions in a new array.
$results = #()
$account = "JohnDoe"
foreach ($s in $server_hostnames) {
$r = (qwinsta /server:$($s) 2> $null | out-string -stream | sls $account)
$results += $r
}
In this example I have 2 servers but in production I would run it against 2-3000 servers, so parallel execution is extremely important.
I have made several attempts at rewriting the code using jobs, workflows, or Split-Pipeline, but with little success.
Usually, filtering with sls (Select-String) doesn't work, not even with findstr.
Split-Pipeline example:
PS C:\SplitPipeline> $server_hostnames | Split-Pipeline {process{ $_; qwinsta /server:$($_) | out-string -stream | sls $account }}
Split-Pipeline : Cannot bind argument to parameter 'Pattern' because it is null.
At line:1 char:21
+ $server_hostnames | Split-Pipeline {process{ $_; qwinsta /server:$($_) | out-str ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Split-Pipeline], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,SplitPipeline.SplitPipelineCommand
Using jobs you could do something like this:
$servers = 'server1', 'server2'
$account = 'JohnDoe'
$results = #()
$sb = {
Param($server, $name)
& qwinsta /server:$server |
Select-String $name -SimpleMatch |
Select-Object -Expand Line
}
$servers | ForEach-Object {
Start-Job -ScriptBlock $sb -ArgumentList $_, $account | Out-Null
}
$results += while (Get-Job -State Running) {
Get-Job -State Complete | ForEach-Object {
Receive-Job -Job $_
Remove-Job -Job $_
}
}
$results += Get-Job -State Complete | ForEach-Object {
Receive-Job -Job $_
Remove-Job -Job $_
}
However, since you want to run qwinsta against several thousand servers you'll probably want to use a job queue rather than running all jobs at the same time, otherwise the massive number of jobs might exhaust your local computer's resources.

powershell Gui progress bar with batch file

I found this gui progress bar on a different site. The way that it explains, should solve my problem which when the job is running not let the gui freeze.
However, since I'm dealing with batch files (installing applications) I need to do a foreach app and install them one by one and not let the gui freeze.
Here is the link to the site link
EDIT: UPDATE the SCRIPT. so far this works but all batch files install at the same time which is causing them to fail. I have more applications but for testing I just added 3.
I am only assuming that the $job is not passing its status to "updatescript" and "completedscript"
$Appname = #("Adobe_FlashPlayer", "Acrobat_Reader, "Microsoft_RDP_8.1")
$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}
$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
#Stop any pending jobs
Stop-JobTracker
}
$timerJobTracker_Tick={
Update-JobTracker
}
#region Job Tracker
$JobTrackerList = New-Object System.Collections.ArrayList
function Add-JobTracker
{
Param (
#[ValidateNotNull()]
#[Parameter(Mandatory = $true)]
[string]$Name,
#[ValidateNotNull()]
#[Parameter(Mandatory = $true)]
[ScriptBlock]$CompletedScript,
[ScriptBlock]$UpdateScript,
[ScriptBlock]$JobScript,
$ArgumentList = $null)
$job = Start-Job -ScriptBlock $JobScript -ArgumentList $ArgumentList
if($job -ne $null)
{
#Create a Custom Object to keep track of the Job & Script Blocks
$psObject = New-Object System.Management.Automation.PSObject
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name Job -Value $job
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name CompleteScript -Value $CompletedScript
Add-Member -InputObject $psObject -MemberType 'NoteProperty' -Name UpdateScript -Value $UpdateScript
[void]$JobTrackerList.Add($psObject)
#Start the Timer
if(-not $timerJobTracker.Enabled)
{
$timerJobTracker.Start()
}
}
elseif($CompletedScript -ne $null)
{
#Failed
Invoke-Command -ScriptBlock $CompletedScript -ArgumentList $null
}
}
function Update-JobTracker
{
<#
.SYNOPSIS
Checks the status of each job on the list.
#>
#Poll the jobs for status updates
$timerJobTracker.Stop() #Freeze the Timer
for($index =0; $index -lt $JobTrackerList.Count; $index++)
{
$psObject = $JobTrackerList[$index]
if($psObject -ne $null)
{
if($psObject.Job -ne $null)
{
if($psObject.Job.State -ne "Running")
{
#Call the Complete Script Block
if($psObject.CompleteScript -ne $null)
{
#$results = Receive-Job -Job $psObject.Job
Invoke-Command -ScriptBlock $psObject.CompleteScript - ArgumentList $psObject.Job
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}
elseif($psObject.UpdateScript -ne $null)
{
#Call the Update Script Block
Invoke-Command -ScriptBlock $psObject.UpdateScript -ArgumentList $psObject.Job
}
}
}
else
{
$JobTrackerList.RemoveAt($index)
$index-- #Step back so we don't skip a job
}
}
if($JobTrackerList.Count -gt 0)
{
$timerJobTracker.Start()#Resume the timer
}
}
function Stop-JobTracker
{
<#
.SYNOPSIS
Stops and removes all Jobs from the list.
#>
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0)
{
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}#endregion
$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false
#Create a New Job using the Job Tracker
foreach ($app in $Appname)
{
$install = "C:\pstools\Update\cmd\$app\install.cmd"
$run = "C:\Windows\System32\cmd.exe"
Add-JobTracker -Name "test"`
-JobScript {
param (
[string]$batchFilePath
)
Write-Verbose "Launching: [$batchFilePath]" -Verbose
Set-Location $env:windir
& ($run, $batchFilePath)
}` -ArgumentList $install -CompletedScript {
Param ($Job)
#$progressbar1.Value = 100
#Enable the Button
$ProgressBarOverlay1.PerformStep()
$buttonStartJob.ImageIndex = -1
$buttonStartJob.Enabled = $true
}`
-UpdateScript {
Param ($Job)
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int])
{
$progressbaroverlay1.Value = $results
}
}
}
}
$timer1_Tick={
#TODO: Place custom script here
#if ([timespan]::FromSeconds($timerUpdate.Tag) -ge [timespan]::Fromminutes(1))
IF($progressbaroverlay1.Value -eq 100)
{
$timerUpdate.stop()
$formSampleTimer.Close()
}
else
{
[System.Windows.Forms.Application]::DoEvents()
$label1.Text = [timespan]::FromSeconds($timer1.Tag++)
}
}
I am assuming that you either copied this from somewhere and modified it to kind of work for you, or you used something like PowerGUI or Sapien to generate the script for you because this is seriously hard to read code in my opinion.
If I understand it right you create $JobTrackerList as an ArrayList object. Then for each application that you want to install you add a PSCustomObject to that ArrayList with 3 properties, one being a Job that runs in the background, one being a script to run when that is completed, and one being a script to update your progress bar. So each object has a background job that is running, and this is the root issue at this point. But wait, we're not done, you have a timer going on (not actually defined in the code you gave us) that runs, and every 1000 ticks it stops, checks each job, if it is completed it runs the "completed" scriptblock that you defined for that object, and then removes the object from the ArrayList. If it is still not completed it updates the progressbar with the "update" scriptblock. Then it starts the timer again.
Here's what I think needs to happen for this to work like you want... You need to go ahead and make your ArrayList, and add objects to it for each application that you want to run, but do not create jobs for them all! Once you create all the objects start a job for the first application (and add it as a member of the object like you did before). Then when your script checks for completed jobs, before it deletes the object it is looking at it should start the job for the next object.
So, that's going to require a bit of re-write of your code. I'm not sure where to really start with this, because the code that you have is so very different to how I would have written it, so I've given you my suggestions, and now I'll let you run with it from here. If you need help writing the code (at least try first) let me know and I'll see what I can do. Probably not today, I'm dead tired and not on my A game, but I'm sure I can come up with something tomorrow if you end up needing help.
Edit: Ok, I think I have code that should do what you want, and work with what you already have in place:
$formJobProgress_Load={
#TODO: Initialize Form Controls here
$timer1.Interval = 1000
$timer1.Tag = 0
$timer1.Start()
[System.Windows.Forms.Application]::DoEvents()
}
$timerJobTracker_Tick={
$timerJobTracker.Stop() #Freeze the Timer
for($index =0; $index -lt $JobTrackerList.Count; $index++){
$psObject = $JobTrackerList[$index]
if($psObject -ne $null -AND $psObject.Job -ne $null -AND $psObject.Job.State -ne "Running"){
#Perform old 'Complete' scriptblock to update progressbar
$ProgressBarOverlay1.PerformStep()
#Check if this is the only job, and if not start the next one
If($JobTrackerList.count -gt 1){
$NextJob = $JobTrackerList[($index+1)]
$NextJob.Job = Start-Job -ScriptBlock $NextJob.JobScript
}
$JobTrackerList.RemoveAt($index)
Remove-Job -Job $psObject.Job
$index-- #Step back so we don't skip a job
}Else{
$results = Receive-Job -Job $Job | Select-Object -Last 1
if ($results -is [int]){
$progressbaroverlay1.Value = $results
}
}
}
if($JobTrackerList.Count -gt 0){
$timerJobTracker.Start()#Resume the timer
}
}
$formMain_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Stop the timer
$timerJobTracker.Stop()
#Remove all the jobs
while($JobTrackerList.Count -gt 0){
$job = $JobTrackerList[0].Job
$JobTrackerList.RemoveAt(0)
Stop-Job $job
Remove-Job $job
}
}
$Appname = #("Adobe_FlashPlayer", "Acrobat_Reader", "Microsoft_RDP_8.1")
$JobTrackerList = New-Object System.Collections.ArrayList
$buttonStartJob_Click= {
$progressbaroverlay1.Value = 0
$progressbaroverlay1.Step = 1
$progressbaroverlay1.Maximum = $Appname.Count
$this.Enabled = $false
$buttonStartJob.Enabled = $false
ForEach($App in $Appname){
$install = "C:\pstools\Update\cmd\$app\install.cmd"
$run = "C:\Windows\System32\cmd.exe"
$JobScript = {
Write-Verbose "Launching: [$install]" -Verbose
Set-Location $env:windir
& $install
}
[void]$JobTrackerList.Add((New-Object PSObject -Property #{
'Application' = $Name
'JobScript' = $JobScript
'Job' = $null
}))
}
$JobTrackerList[0].Job = Start-Job -ScriptBlock $JobTrackerList[0].JobScript
}
$timer1_Tick={
#TODO: Place custom script here
#if ([timespan]::FromSeconds($timerUpdate.Tag) -ge [timespan]::Fromminutes(1))
IF($progressbaroverlay1.Value -eq 100)
{
$timerUpdate.stop()
$formSampleTimer.Close()
}
else
{
[System.Windows.Forms.Application]::DoEvents()
$label1.Text = [timespan]::FromSeconds($timer1.Tag++)
}
}
I hope that works for you.

Script to identify Process Name , Memory , Account Owner in Windows server

I am running a web service in Windows machine , and i wanted to know the Memory used by each user while accessing this Web service.
In Task Manager I could see the ProcessName, UserName, Memory.
Is there a way to get the same by running powershell or batch script?
Please help.
There's may be a cleaner way to do this, but here's an example:
$users = #{}
$process = Get-Process
Get-WmiObject Win32_SessionProcess | ForEach-Object {
$userid = (($_.Antecedent -split “=”)[-1] -replace '"' -replace “}”,“”).Trim()
if($users.ContainsKey($userid))
{
#Get username from cache
$username = $users[$userid]
}
else
{
$username = (Get-WmiObject -Query "ASSOCIATORS OF {Win32_LogonSession.LogonId='$userid'} WHERE ResultClass=Win32_UserAccount").Name
#Cache username
$users[$userid] = $username
}
$procid = (($_.Dependent -split “=”)[-1] -replace '"' -replace “}”,“”).Trim()
$proc = $process | Where-Object { $_.Id -eq $procid }
New-Object psobject -Property #{
UserName = $username
ProcessName = $proc.Name
"WorkingSet(MB)" = $proc.WorkingSet / 1MB
}
}
OUTPUT:
UserName ProcessName WorkingSet(MB)
-------- ----------- --------------
Frode taskhostex 61,5
Frode explorer 172,33203125
Frode RuntimeBroker 21,9375
Frode HsMgr 5,578125
Frode HsMgr64 5,453125
Frode SetPoint 17,4375
The code needs to run as admin to get sessions for other users(not just the current user).
Have you tried get-process?
You can run that and filter by various factors. You can use -name or -id to filter by process name or PID. ex:
get-process -name iexplore
get-process -Id 0 # this returns the idle process.
Or, you can filter by other factors
Get processes using more than 1MB of memory
get-process |Where-object {$_.WorkingSet64 -gt 1048576}
More info on get-process here: http://technet.microsoft.com/en-us/library/ee176855.aspx

Resources