powershell wait-job doesn't wait - powershell

I'm trying to get a set instructions to wait until a process is finished before they start but I keep having the variables try to run straight after.
I would try a wait() but I cant know how long the scan will take.
#Variables
$logPath = "C:\Users\Administrator\Desktop\log.txt"
$scanPath = "C:\Users\Administrator\Desktop\scan.txt"
$repairLog = "C:\Users\Administrator\Desktop\repair.txt"
$failLog = "C:\Users\Administrator\Desktop\fail.txt"
$computer = "SERVER2012"
$CBSFileLocation = "C:\Windows\Logs\CBS\CBS.log"
$date = Get-Date
$sevenDays = New-Object System.TimeSpan 7,0,0,0,0
$repair = "Repair"
$fail = "fail"
$then = $date.Subtract($sevenDays)
#Start of Code:
$EventLog = Get-EventLog -LogName System -ComputerName $computer -After $then -Before $date -EntryType Error | Out-File $logPath
Start-Job -Name SFC {Start-Process sfc /scannow}
#I want the wait/suspend here.
Wait-Job -Name SFC {
$ScanX = Get-Content $CBSFileLocation
$ScanX | Out-File $scanPath
Select-String -Path $scanPath -Pattern $repair | Out-File $repairLog
Select-String -Path $scanPath -Pattern $fail | Out-File $failLog
echo "Done!!"
}

I'm guessing you want to wait, and when SFC is ready, execute the script inside the last { ... } in your scripts.
As far as I can tell from reading the documentation for Wait-Job, there is no scriptblock parameter what would allow for this.
Instead try rearranging your code a little, like this
#Variables
$logPath = "C:\Users\Administrator\Desktop\log.txt"
$scanPath = "C:\Users\Administrator\Desktop\scan.txt"
$repairLog = "C:\Users\Administrator\Desktop\repair.txt"
$failLog = "C:\Users\Administrator\Desktop\fail.txt"
$computer = "SERVER2012"
$CBSFileLocation = "C:\Windows\Logs\CBS\CBS.log"
$date = Get-Date
$sevenDays = New-Object System.TimeSpan 7,0,0,0,0
$repair = "Repair"
$fail = "fail"
$then = $date.Subtract($sevenDays)
#Start of Code:
$EventLog = Get-EventLog -LogName System -ComputerName $computer -After $then -Before $date -EntryType Error | Out-File $logPath
#Start the job and return the prompt once the job has State Completed
Start-Job {Start-Process sfc /scannow} | Wait-Job
#These lines are executed once Wait-Job above returns the prompt
$ScanX = Get-Content $CBSFileLocation
$ScanX | Out-File $scanPath
Select-String -Path $scanPath -Pattern $repair | Out-File $repairLog
Select-String -Path $scanPath -Pattern $fail | Out-File $failLog
echo "Done!!"

Related

Write-Output inside Invoke-Command scriptblock

Below is a script I'm working on to get all SQL-jobs into a CSV-file.
The script itself is working great but I have trouble with the error-handling.
I can't figure out how to get the Out-File inside the Catch-block to print to the file on my local machine instead of the remote machine I'm running the Invoke-Command to.
How do I accomplish this?
Thanks
PS. The script is written out fully as much as possible for non experienced co-workers convenience
$sqlServers = #("TEST1","TEST2")
$filePath = [Environment]::GetFolderPath("Desktop")
$dateToday = Get-Date -Format “yyMMdd HH:mm"
$dateTodayFile = Get-Date -Format “yyMMdd"
Write-Output "$dateToday $sqlServers" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output = Invoke-Command -ComputerName $sqlServers -ScriptBlock{
Try
{
Import-Module sqlserver -ErrorAction Stop
}
Catch
{
Write-Output "$dateToday ERROR $env:computername" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
$instances = $env:computername | Foreach-Object {Get-ChildItem -Path "SQLSERVER:\SQL\$_"}
ForEach ($instance in $instances){
Try
{
$instanceName = $instance.InstanceName
Get-SqlAgentJob -ServerInstance "$env:computername\$instanceName" -ErrorAction Stop |
Where-Object {$_.IsEnabled -eq "True" -and $_.LastRunDate -gt [DateTime]::Today.AddDays(-2) -and $_.OwnerLoginName -match "TEST"} |
Select-Object #{Name=‘Job name‘;Expression={$_.Name}},
#{Name=‘Description‘;Expression={$_.Description}},
#{Name=‘Instance‘;Expression={$_.Parent -Replace '[][]'}},
#{Name=‘Run outcome‘;Expression={$_.LastRunOutcome}},
#{Name=‘Run date‘;Expression={$_.LastRunDate}},
#{Name=‘Run duration‘;Expression={$_.LastRunDuration}},
#{Name=‘Job creator‘;Expression={$_.OwnerLoginName}},
#{Name=‘Runs on a schedule‘;Expression={$_.HasSchedule}},
#{Name='Schedule Type';Expression={$_.JobSchedules -join ','}}
}
Catch
{
Write-Output "$dateToday ERROR $env:computername\$instanceName" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
}
}
$output | Select-Object -Property * -ExcludeProperty PSComputerName,RunSpaceID,PSShowComputerName |
Sort-Object "Job name" |
Export-Csv $filePath\SQLJobInvent$dateTodayFile.csv -NoTypeInformation -Delimiter ";" -Encoding UTF8
Write-Output "$dateToday $filePath" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Write-Output "----------------------------------------" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Your primary issue is scope.
The $dateToday, $filePath and $dateTodayFile are all declared on the local machine, but you're trying to use them on the remote computer (script block) where they are undefined.
There are a few ways to get your variables passed to the remote computer, below are two:
# Add desired variable to ArgumentList and define it as a parameter
Invoke-Command -ComputerName $sqlServers -ArgumentList $dateToday,$filePath,$dateTodayFile -ScriptBlock {
param(
$folderPath,
$filePath,
$dateTodayFile
)
# Do something with our injected variables
Write-Output "$dateToday ERROR $env:computername" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
}
OR
# In PS ver >= 3.0 we can use 'using'
Invoke-Command -ComputerName $serverName -ScriptBlock {Write-Output $using:dateToday}

Using Start-Job on script

Using a script in PowerShell to recursivly pass through all folders on multiple NAS boxes to display every folder with its full path in an Out-File.
Using the Get-FolderEntry script I found here.
Since I have multiple NAS boxes with more then 260 chars in the filename/pathlength I figured I'd use multithreading to speed the process up.
Code:
. C:\Users\mdevogea\Downloads\Get-FolderEntry.ps1
# list with the servers
$Computers = Get-Content C:\Users\mdevogea\Desktop\servers.txt
# scriptblock calling on get-FolderEntry
$sb = {
param ($Computer, $fname)
C:\Users\mdevogea\Downloads\Get-FolderEntry.ps1 -Path $Computer |
fl | Out-File -Append -Width 1000 -FilePath $fname
}
foreach($Computer in $Computers)
{
$name = $Computer.Replace("\", "")
$fname = $("C:\Users\mdevogea\Desktop\" + $name + ".txt")
#Get-FolderEntry -Path $Computer | fl | Out-File -Append -Width 1000 $fname
$res = Start-Job $sb -ArgumentList $Computer, $fname
}
# Wait for all jobs
Get-Job
while(Get-Job -State "Running")
{
Write-Host "Running..."
Start-Sleep 2
}
# Get all job results
Get-Job | Receive-Job | Out-GridView
So far:
I either get empty files with the correct naming of the file.
I get the correct named file with the code of Get-FolderEntry in it.
I get errors depend on what I pass along to the scriptblock.
In short, it's probably stupid but don't see it.
Found it eventually myself after some trial and error:
. C:\Users\mdevogea\Downloads\Get-FolderEntry.ps1
# list with the servers
$Computers = Get-Content C:\Users\mdevogea\Desktop\servers.txt
# scriptblock calling on get-FolderEntry
$sb = {
Param ($Computer, $fname)
. C:\Users\mdevogea\Downloads\Get-FolderEntry.ps1
(Get-FolderEntry -Path $Computer | fl | Out-File -Append -Width 1000 -FilePath $fname)
}
foreach ($Computer in $Computers)
{
$name = $Computer.Replace("\", "")
$fname = $("C:\Users\mdevogea\Desktop\" + $name + ".txt")
$res = Start-Job $sb -ArgumentList $Computer, $fname
}
# Wait for all jobs
Get-Job
while (Get-Job -State "Running")
{
Write-Host "Running..."
Start-Sleep 2
}
# Get all job results
Get-Job | Receive-Job | Out-GridView
Thanks a lot Ansgar for pointing my in the right direction!

Invoke-Command: Null-value and positional parameter errors

I wanted to start a new thread for this, since I am using a different method in my code now. I have written a script that pings hundreds of devices and logs their online or offline status. It was taking an extremely long time to run, so I am now looking into using Invoke-Command to run the commands remotely on servers for each site (instead of all from the same server). I am receiving the following errors: "A positional parameter cannot be found that accepts argument 'Of'", "You cannot call a method on a null-valued expression", and this is happening for each server as it is iterating through them. Any ideas as to why this is happening? Thank you very much, here is my current code:
<#
.NOTES
===========================================================================
Created on: 11/17/2016 8:06 AM
Created by:
Organization:
Filename: Get-MPOSOfflinePrinters.ps1
===========================================================================
.DESCRIPTION
#>
#Define log file variables and remove any existing logs
$logfile = "D:\Logs\MPOSPrinterPingLog.txt"
$offlineprinters = "D:\Reports\MPOS\MPOSOfflinePrinters.txt"
If (Test-Path $logfile) {Remove-Item $logfile}
If (Test-Path $offlineprinters) {Remove-Item $offlineprinters}
Add-Content $logfile "Gathering server list"
#Compiling list of all MPOS Print Servers
$serverList = (Get-ADComputer -Filter "Name -like 'Q0*P30' -or Name -like 'Q0*P32'" -SearchBase "OU=Print,OU=Prod,OU=POS,DC=COMPANY,DC=NET").name | Sort-Object | Out-File C:\Temp\MPOS\MPOSPrintServers.txt
$serverListPath = "C:\Temp\MPOS\MPOSPrintServers.txt"
Add-Content $logfile "Compiling text file"
#Retrieve a list of MPOS Print servers from text file and set to variable $serverNames
$serverNames = Get-Content -Path $serverListPath
Invoke-Command -ComputerName $serverNames -ScriptBlock {
#Define log file variables and remove any existing logs
$logfile = "C:\Temp\MPOSPrinterPingLog.txt"
$offlineprinters = "C:\Temp\MPOSOfflinePrinters.txt"
$masteroffline = "\\a0345p689\d$\Reports\MPOS\MPOSOfflinePrinters.txt"
If (Test-Path $logfile) {Remove-Item $logfile}
If (Test-Path $offlineprinters) {Remove-Item $offlineprinters}
#process xml file to parse IP addresses for ping
$timestamp2 = (Get-Date -Format g)
Add-Content $logfile "$timestamp2 - Processing xml file from $serverName to parse data to csv"
$xml = [xml](Get-Content C:\ProgramData\Microsoft\Point Of Service\Configuration\Configuration.xml)
$PrinterNames = $xml.selectNodes('//PointOfServiceConfig/ServiceObject/Device') | foreach {New-Object -TypeName psobject -Property #{LogicalName=$_.LogicalName.Name}}
$PrinterIPs = $xml.selectNodes('//PointOfServiceConfig/ServiceObject/Device') | foreach {New-Object -TypeName psobject -Property #{HardwarePath=$_.HardwarePath}}
foreach ($PrinterIP in $PrinterIPs) {
$pingableIP = $PrinterIP.HardwarePath
If (Test-Connection $pingableIP -Quiet -Count 1) {
$timestamp3 = (Get-Date -Format g)
Add-Content $logfile "$timestamp3 - $serverName, $pingableIP is online and pingable"
}
Else {
$timestamp3 = (Get-Date -Format g)
Add-Content $offlineprinters "$timestamp3 - $serverName, $pingableIP is offline!"
}
Get-Content $offlineprinters | Out-File -FilePath $masteroffline -Append -NoClobber
} #foreach ($PrinterIP in $PrinterIPs) {
} #Invoke-Command -ComputerName $serverNames -ScriptBlock {

How to update variables in script?

Script should restart service and save current data and time in file after memory usage reach the limit. But it doesn't save current time and in second if when service is stopped it doesn't do else. It always show the same values in $data and $stat. I am not sure where I made mistake.
$proc = 'process name'
$serv = 'service name*'
$ram = 10MB
$inter = 1
$data = Get-Date -format "yyyy/MM/dd HH:mm:ss"
$log = "c:\log.txt"
$stat = Get-Process $proc -EA SilentlyContinue
while ($true) {
if ((Get-Process $proc -EA SilentlyContinue | Select-Object -Exp ws) -gt $ram) {
Restart-Service $serv
Add-Content -path $log -value ($data + "`t" + "Restarting")
Start-Sleep -m 10000
if ($stat -ne $null) {
Add-Content -path $log -value "Working"
} else {
Start-Service $serv
Add-Content -path $log -value ($data + "`t" + "Starting")
}
}
Start-Sleep -s $inter
}
The statements in your variable assignments are evaluated at the time of assignment, not when the variable is used. To get the latter you could define the statements as scriptblocks:
$data = { Get-Date -format 'yyyy\/MM\/dd HH:mm:ss' }
$stat = { Get-Process mysqld -ea SilentlyContinue }
Use the call operator (&) in an expression (()) or subexpression ($()) to evaluate the scriptblocks at a later point in your script:
if ((&$stat) -ne $null) {
Add-Content -Path $log -Value "Working"
} else {
Start-Service $serv
Add-Content -Path $log -Value "$(&$data)`tStarting"
}
Probably a more convenient way is to define the operations as functions:
function Get-Timestamp { Get-Date -format 'yyyy\/MM\/dd HH:mm:ss' }
function Test-MySQLProcess { [bool](Get-Process mysqld -EA SilentlyContinue) }
and use them like this:
if (Test-MySQLProcess) {
Add-Content -Path $log -Value "Working"
} else {
Start-Service $serv
Add-Content -Path $log -Value "$(Get-Timestamp)`tStarting"
}
As a side note, you should escape forward slashes in date format strings, otherwise Windows substitutes them with whatever date separator character is configured in your system's regional settings.

How to put a job on wait in powershell

HI every one I Have the following scripts which i am working on but not sure how to put a wait for a zip to finish and than move on to the next block of code. following are the two scripts which i am using. The first script is a backup script which is calling another script for zipping
$Date = Get-Date
$folder_date = $Date.ToString("yyyy-MM-dd_HHmm")
$backup_folder_name = 'c:\Russia\' + $folder_date
$V3_folder_name = 'C:\121RussiaScaled\products'
$Admin_folder_name = 'C:\inetpub\wwwroot\admin'
$Tablet_folder_name = 'C:\inetpub\wwwroot\tabl'
$Service_folder_name = 'C:\Russia\dll'
if (!(Test-Path -path $backup_folder_name)) {
New-Item $backup_folder_name -type directory
} # if (!(Test-Path -path D:Data))
if ((Test-Path -path $V3_folder_name)) {
Start-job -scriptblock {gi $V3_folder_name | .\Library\out-zip.ps1
$backup_folder_name\V3.zip $_}
Wait-job -Id $Job.Id
}
if ((Test-Path -path $Service_folder_name)) {
$Job = Start-job -scriptblock {gi $Service_folder_name | .\Library\out-zip.ps1
$backup_folder_name\Services.zip $_}
Wait-job -Id $Job.Id
}
if ((Test-Path -path $Admin_folder_name)) {
$Job = Start-job -scriptblock {gi $Admin_folder_name | .\Library\out-zip.ps1
$backup_folder_name\admin.zip $_}
Wait-job -Id $Job.Id
}
if ((Test-Path -path $Tablet_folder_name)) {
$Job = Start-job -scriptblock {gi $Tablet_folder_name | .\Library\out-zip.ps1
$backup_folder_name\tablet.zip $_}
Wait-job -Id $Job.Id
}
This is my out.zip script
$path = $args[0]
$files = $input
write-output $path
if (-not $path.EndsWith('.zip')) {$path += '.zip'}
if (-not (test-path $path)) {
set-content $path ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18))
}
$ZipFile = (new-object -com shell.application).NameSpace($path)
$files | foreach {$zipfile.CopyHere($_.fullname)}
while using the above script i am getting an error "Start-Job missing an argument for the paratmeter script block."
or is there another way so that i can put a wait for these zips to finish one by one
Try
$Job = Start-Job -ScriptBlock .....
Wait-Job -Id $Job.Id
For the ScriptBlock error, try specifying the starting brace at the same line like:
$Job = Start-Job -ScriptBlock {
# Job code here
}