scriptblock returning object for compare-object - powershell

I'm writing a script to compare 2 samba locations via compare object.
To speed things up i would like to give each location via a thread to a scriptblock where i let the object get made.
After that i'd like the output from the scriptblock as an object to use it in the Compare-Object cmdlet.
What i have sofar:
$nas_smb_share = "\\nas\loc\"
$cs_dest ="\\dest2\loc"
$check_hash = {
Param($loc)
$fso = (dir $loc -Recurse | Where-object{(!$_.psiscontainer) -AND ($_.LastWriteTime -gt (Get-Date).AddHours(-20))} | get-hash -Algorithm MD5)
return $fso
}
$compare_loc =#($nas_smb_share, $cs_dest)
foreach ($check in $compare_loc)
{
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le 3)
{
$j = Start-Job -ScriptBlock $check_hash -ArgumentList $check -Name $check
} else
{
$running | Wait-Job
}
Get-Job | Receive-Job
$test = Receive-Job -Name $nas_smb_share -Keep
$test2 = Receive-Job -Name $cs_dest -Keep
}
Get-Job | Wait-Job | Receive-Job
so would still need to add this in somewhere:
(Compare-Object -ReferenceObject $fso -DifferenceObject $fsoBU -Property hash -PassThru).Path | %{if ($_.SideIndicator -eq "=>" ){$result = ("$($_.InputObject)")}}
(dir $cs_dest -Recurse | Where-Object {(!$_.psiscontainer)} | get-hash -Algorithm MD5 | ? {$_.hashstring -match $result})
But the result from test and test2 are hashtable (i think?) and not an object.
Any input would be appreciated on where i went wrong, or how i could do it differently

If you want to return the names of the files in the second location whose checksums don't match, the following editions would help.
$nas_smb_share = "\\nas\loc\"
$cs_dest = "\\dest2\loc"
$compare_loc = #($nas_smb_share, $cs_dest)
$check_hash = {
Param($loc)
return Get-ChildItem $loc -Recurse | Where-object {(!$_.psiscontainer) -AND ($_.LastWriteTime -gt (Get-Date).AddHours(-20))} | Get-FileHash -Algorithm MD5
}
$Jobs = #()
foreach ($check in $compare_loc) {
$Jobs += Start-Job -ScriptBlock $check_hash -ArgumentList $check -Name $check
}
$Jobs | Wait-Job | Out-Null
$test = Receive-Job -Name $nas_smb_share -Keep
$test2 = Receive-Job -Name $cs_dest -Keep
(Compare-Object -ReferenceObject $test -DifferenceObject $test2 -Property Hash -PassThru | Where-Object { $_.SideIndicator -eq "=>" }).Path

Related

Send variables from inside Invoke-Command scriptblock back to host

The below is a script that is collecting information about SQL-jobs on remote servers.
However, I want to send the information inside the catch-block back to the host.
As the script is written now the script is printing to a logfile on each remote server.
How can I send the information back to the host?
$sqlServers = #("SERVER1","SERVER2")
$runningHost = "$env:computername"
$filePath = "C:\SQLJobInventory"
$desktopPath = [Environment]::GetFolderPath("Desktop")
$output = Invoke-Command -ComputerName $sqlServers -ArgumentList $filePath,$dateToday,$dateTodayFile -ScriptBlock{
param
(
$filePath,
$dateToday,
$dateTodayFile
)
$runningHostRemote = $env:computername
Try
{
Import-Module sqlserver -ErrorAction Stop
$instances = $runningHostRemote | Foreach-Object {Get-ChildItem -Path "SQLSERVER:\SQL\$_"} -ErrorAction Stop
}
Catch
{
Write-Output "$dateToday [ERROR] $runningHostRemote" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Exit
}
ForEach ($instance in $instances)
{
Try
{
$instanceName = $instance.InstanceName
Get-SqlAgentJob -ServerInstance "$runningHostRemote\$instanceName" -ErrorAction Stop |
Where-Object {$_.IsEnabled -eq "True" -and $_.LastRunDate -gt [DateTime]::Today.AddDays(-2) -and $_.OwnerLoginName -match "LKL"} |
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] $runningHostRemote\$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 [INFO] $filePath\Log$dateTodayFile.txt" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append
Change write-output to return
Catch
{
Return "$dateToday [ERROR] $runningHostRemote\$instanceName"
}
Return will exit the script block and pass your string back to the output variable.
I have solved it by creating my own properties of the output-variable with New-Object.
There is probably a better way to do it but this was the most convinient.
The Return-method did not work for me in this particular script.
$runningHost = "$env:computername"
$filePath = "C:\SQLJobInventory"
$lastResortPath = [Environment]::GetFolderPath("Desktop")
$dateToday = Get-Date -Format “yyMMdd HH:mm"
$dateTodayFile = Get-Date -Format “yyMMdd"
$output = Invoke-Command -ComputerName $sqlServers -ArgumentList $filePath,$dateToday,$dateTodayFile -ScriptBlock{
param
(
$filePath,
$dateToday,
$dateTodayFile
)
$runningHostRemote = $env:computername
Try
{
Import-Module sqlserver -ErrorAction Stop
$instances = $runningHostRemote | Foreach-Object {Get-ChildItem -Path "SQLSERVER:\SQL\$_"} -ErrorAction Stop
}
Catch
{
$moduleError = #{moduleError="$dateToday [ERROR] $runningHostRemote"}
New-Object -Type PSObject -Property $moduleError
Exit
}
ForEach ($instance in $instances){
Try
{
$instanceName = $instance.InstanceName
$jobSuccess = #{jobSuccess="$dateToday [INFO]"}
New-Object -Type PSObject -Property $jobSuccess
Get-SqlAgentJob -ServerInstance "$runningHostRemote\$instanceName" -ErrorAction Stop |
Where-Object {$_.IsEnabled -eq "True" -and $_.LastRunDate -gt [DateTime]::Today.AddDays(-2) -and $_.OwnerLoginName -match "LKL"} |
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
{
$jobError = #{jobError="$dateToday [ERROR] $runningHostRemote\$instanceName"}
New-Object -Type PSObject -Property $jobError
Exit
}
}
}
$output | Select-Object -ExpandProperty moduleError -ErrorAction SilentlyContinue | Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output | Select-Object -ExpandProperty Jobsuccess -ErrorAction SilentlyContinue | Out-File "$filePath\Log$dateTodayFile.txt" -Append
$output | Select-Object -ExpandProperty jobError -ErrorAction SilentlyContinue | Out-File "$filePath\Log$dateTodayFile.txt" -Append
$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 [INFO] $filePath\Log$dateTodayFile.txt" |
Out-File "$filePath\Log$dateTodayFile.txt" -Append

Counting returned objects from jobs in powershell?

quick question, I've got a short little script that creates 2 background jobs and pings some websites, now since jobs cannot return variables apparently I figured I'd try it as follows (which is probably a stupid way of doing it but okay).
Now the weird thing is that it seems to work somewhat? But the count it returns is nowhere near what it should be, it's very random, sometimes it stops at 2, sometimes at 3, can anyone maybe tell me what I'm doing wrong? Shouldn't $i end up equalling 7?
Initially I wanted parallel runspaces but I've been trying to wrap my head around that for weeks now and I've come to the conclusion that I'm too dumb for that apparently.
Is what I'm trying to do here even possible?
$1 = #('google.com', 'bing.com', 'yahoo.com', 'startpage.com')
$2 = #('reddit.com', 'facebook.com', 'nonexistent.com')
function run {
$pwd = Get-Location
$i = 0
$list = {
function list1 ($1, $pwd) {
foreach ($ping in $1) {
"&"
if (Test-Connection -ComputerName $ping -Count 1 -Quiet) {
"$pwd\" + "$ping"
}
}
}
function list2 ($2, $pwd) {
foreach ($ping in $2) {
"&"
if (Test-Connection -ComputerName $ping -Count 1 -Quiet) {
"$pwd\" + "$ping"
}
}
}
}
echo "ping?`nyes or no`n"; $y = Read-Host
if ($y -eq 'y') {
Start-Job -ScriptBlock { list1 $using:1 $using:pwd } -InitializationScript $list
Start-Job -ScriptBlock { list2 $using:2 $using:pwd } -InitializationScript $list
While (Get-Job | Where-Object HasMoreData) {
Get-Job | Where-Object HasMoreData | Receive-Job | Where-Object { $_ -notlike "&" } | Tee-Object -FilePath ping.txt -Append
Get-Job | Where-Object HasMoreData | Receive-Job | Where-Object { $_ -like "&" } | Foreach-Object { $i += 1 }
}
$i | Add-Content totalcount.txt
}
}
Fix to the script is below.
Everytime you use Receive-Job, it takes the data from stream and displays it. After displaying it, that data is no longer accessible via Receive-job. To fix this, either Use -Keep switch or store the data you get from Receive-Object in a variable to use it multiple times.
if ($y -eq 'y') {
Start-Job -ScriptBlock { list1 $using:1 $using:pwd } -InitializationScript $list
Start-Job -ScriptBlock { list2 $using:2 $using:pwd } -InitializationScript $list
While (Get-Job | Where-Object HasMoreData) {
Get-Job | Where-Object HasMoreData | Receive-Job -Keep | Where-Object { $_ -notlike "&" } | Tee-Object -FilePath ping.txt -Append
Get-Job | Where-Object HasMoreData | Receive-Job | Where-Object { $_ -like "&" } | Foreach-Object { $i += 1 }
}
$i | Add-Content totalcount.txt
}
I would however recommend a little different route than what you have. Instead of getting the data while you are checking to see if the job completed, run the loop and output this way,
if ($y -eq 'y') {
Start-Job -ScriptBlock { list1 $using:1 $using:pwd } -InitializationScript $list
Start-Job -ScriptBlock { list2 $using:2 $using:pwd } -InitializationScript $list
While ((Get-Job | ? {$_.State -eq "Running" }).Count -gt 0) {
# wait for all jobs to complete...
Start-Sleep 1
}
#Once all the jobs are done, you can check the results.
$data = Get-Job | Receive-Job | Where-Object { $_ -notlike "&" }
$data | Tee-Object -FilePath ping.txt -Append
$data.Count | Add-Content totalcount.txt
Get-Job | Remove-Job # Dont forget to clean up.
}

Receive-Job Powershell

I'm trying to learn about how to properly use Receive-Job. My objective is to essentially have a job start and run something like this:
$Alldatasets = [PSCustomobject]#{}
$Alldatasets = if(Test-Path "Alldatasets"){
Get-ChildItemContent "Alldatasets" | ForEach {$_.Content | Add-Member #{Name = $_.Name} -PassThru} | Where Location -EQ $Location | Where {$Dataset.Count -eq 0 -or (Compare-Object $Dataset $_.Name -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0}
}
$Alldatasets
Then return the output $Alldatasets back to the main script, which you can't see, but would be a table of custom objects.
I understand the process of receive-job, but nothing really tutorial-wise how to use it to return the custom object-table that I am making in the above example. Using wait-job | receive-job I get just the actual process details when I use Get-Member- $Alldatasets doesn't seem to be anywhere, but running the .ps1 script I wrote it in- It posts $Alldatasets. I just can't get $Alldatasets from receive-job.
You have to use Start-Job to be able to use 'Receive-Job`. And if you are going to use variables in it, you have to pass it as arguments because you are essentially spinning up a new process/thread and it will not have access to your local variables. Hence the param block.
$ScriptBlock = {
param(
[Parameter (Mandatory=$true,
Position = 0)]
$Location,
[Parameter (Mandatory=$true,
Position = 1)]
$DataSet
)
$Alldatasets = [PSCustomobject]#{}
$Alldatasets = if(Test-Path "Alldatasets"){
Get-ChildItemContent "Alldatasets" | ForEach {$_.Content | Add-Member #{Name = $_.Name} -PassThru} | Where Location -EQ $Location |
Where {$Dataset.Count -eq 0 -or (Compare-Object $Dataset $_.Name -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0}}
Return $Alldatasets
}
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $Location, $DataSet -Name "MyCustomJob"
$Alldatasets = Get-Job -Name "MyCustomJob" | Wait-Job | Receive-Job
I figured it out. It wasn't an issue of passing the object. It was an issue of directory.
It doesn't tell you in any of the tutorials I have found, so I will state it here.
Start-Job does not specify a location when starting. My above script performed Test-Path command with the assumption it was started in the original working directory. I had to specify a working directory in order for script to work:
$ScriptBlock = {
param(
[Parameter (Mandatory=$true,
Position = 0)]
$Location,
[Parameter (Mandatory=$true,
Position = 1)]
$WorkingDir,
[Parameter (Mandatory=$true,
Position = 3)]
$DataSet
)
Set-Location "$WorkingDir"
$Alldatasets = [PSCustomobject]#{}
$Alldatasets = if(Test-Path "Alldatasets"){
Get-ChildItemContent "Alldatasets" | ForEach {$_.Content | Add-Member #{Name = $_.Name} -PassThru} | Where Location -EQ $Location |
Where {$Dataset.Count -eq 0 -or (Compare-Object $Dataset $_.Name -IncludeEqual -ExcludeDifferent | Measure).Count -gt 0}}
Return $Alldatasets
}
Start-Job -ScriptBlock $ScriptBlock -ArgumentList $Location, $DataSet, $WorkingDir -Name "MyCustomJob"
$Alldatasets = Get-Job -Name "MyCustomJob" | Wait-Job | Receive-Job

Is it possible to speed up scanning a list of computers for specific service names?

I'm kind of a newbie to PowerShell and I am currently making a simple service monitoring script. Right now I have a list of computer names and a list of service names that I scan for.
I save the scan to a log. I am wondering if there is any way I can speed up my PowerShell code? I'm not sure if I am using the quickest methods for the job.
Are there any known alternatives to this code that would scan services quicker?
$myServices = $PSScriptRoot + "\services.txt" # $PSScriptRoot references current directory
$myServers = $PSScriptRoot + "\servers.txt"
$Log = $PSScriptRoot + "\svclog.csv"
$LogLive = $PSScriptRoot + "\svclogLive.csv"
$serviceList = Get-Content $myServices
Remove-Item -Path $Log
$results = Get-Content $myServers | ForEach-Object {
foreach ($service in $serviceList) {
if ($s=get-service -computer $_ -name $service -ErrorAction SilentlyContinue)
{
$s | select MachineName, ServiceName, Status, StartType
} else {
# "$_ - Service '$service' does not exist."
}
}
}
$results | Export-CSV $Log -notypeinformation
# Create a second current log that Python can read while this script runs
Copy-Item -Path $Log -Destination $LogLive
Use Invoke-command
$serviceList = Get-Content $myServices
#some code
$results = Get-Content $myServers
Invoke-command -ComputerName $results -ScriptBlock {
Param($MyServices)
Get-Service -Name $MyServices | Select-Object -Property ServiceName, Status, StartType
} -ArgumentList $MyServices,$Null | Select-Object -Property ServiceName, Status, StartType,PSComputerName |
Export-Csv -NoTypeInformation -Path $Log
#For getting starttype in Version 2.0
Get-wmiObject -class Win32_Service -Filter "Name='BITS'" | Select-Object -Property Name, State, startMode
You can try capturing all of the target server's services in an array and looking through it rather than calling get-service on every service you are searching for:
$myServices = $PSScriptRoot + "\services.txt" # $PSScriptRoot references current directory
$myServers = $PSScriptRoot + "\servers.txt"
$Log = $PSScriptRoot + "\svclog.csv"
$LogLive = $PSScriptRoot + "\svclogLive.csv"
$serviceList = Get-Content $myServices
Remove-Item -Path $Log
$results = Get-Content $myServers | ForEach-Object {
# All of the services in one grab
$serverServices = #(Get-Service -computer $_ -ErrorAction SilentlyContinue)
if ($serverServices) {
foreach ($service in $serviceList) {
#Note: this inner use of another $_ may confuse PowerShell...
if ($s = ($serverServices | Where {$_.Name -eq $service}))
{
$s | select MachineName, ServiceName, Status, StartType
} else {
# "$_ - Service '$service' does not exist."
}
}
}
}
$results | Export-CSV $Log -notypeinformation
# Create a second current log that Python can read while this script runs
Copy-Item -Path $Log -Destination $LogLive

Start services in parallel

I have a script which checks if certain service on different servers is up, if it is not, the script should start the service.
The problem is, it doesn't start the services in parallel, instead it waits until each service is started.
Code:
$server_list = Get-Content -path D:\Path\list_of_servers.txt
$server_list | foreach {
(Get-Service -Name '*Service Name*' -computername $_) | Where-Object {$_.status -eq "Stopped"} | Set-Service -Status Running
}
I know it's due to the way the script is written, but maybe some of you have any suggestions how to make it better?
Cheers!
Here is an example of parallel processing using Powershell and Workflows:
$server_list = Get-Content -path D:\Path\list_of_servers.txt
workflow MyWorkflow
{
foreach -parallel($s in $server_list) {
inlinescript { (Get-Service -Name '*Service Name*' -PSComputerName $s) | Where-Object {$_.status -eq "Stopped"} | Set-Service -Status Running
}
}
}
Using Powershell V2 and jobs
Untested code, but should be close:
$server_list = Get-Content -path D:\Path\list_of_servers.txt
$maxJobs = 5
$scriptblock = {
(Get-Service -Name $args[1] -ComputerName $args[0]) | Where-Object {$_.status -eq "Stopped"} | Set-Service -Status Running
}
foreach ($s in $server_list)
{
$arguments = #($s, $service)
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
while ($running.Count -gt ($maxJobs -1)) {
$done = Get-Job | Wait-Job -Any
$running = #(Get-Job | ? {$_.State -eq 'Running'})
}
start-job -scriptblock $scriptblock -ArgumentList $arguments
}
Get-Job | Wait-Job
Get-Job | Receive-Job