Receive-Job Powershell - 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

Related

Function within a Function - Powershell

OK I am going to try to explain this as best as I can. What started out as a simple script has turned into a huge mess and now I cannot figure out how to get it working. I have been coming here for answers for some time so maybe you guys can help.
What I am trying to do is a import a list of systems and check to see if they are online. If they are online they go in one list and if not they go in another.
foreach ($server in $servers) {
if (Test-Connection $server -Count 1 -ea 0 -Quiet) {
Write-Host "$server Is Up" -ForegroundColor Green
$server | out-file -Append $liveSystems -ErrorAction SilentlyContinue
} else {
Write-Host "$server Is Down" -ForegroundColor Red
$server | out-file -Append $inactive -ErrorAction SilentlyContinue
}
}
From there I check to see if the application I need installed is on the systems. That is where things start to go off-track. When I run the function to process the $liveSystems file all I get is the last line of the file (or the same system over and over) and not each system as it should be.
function Is-Installed( $program ) {
$x86 = ((Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
$x64 = ((Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
}
$program
function process-file1 {
param($filename)
Get-Content $filename -PipelineVariable line | ForEach-Object {
Is-Installed -program "My_Service"
if (Is-Installed -eq "True") {
Write-Host "$server has agent installed" -ForegroundColor Green
$server | Out-File $installed -ErrorAction SilentlyContinue
}
else
{
Write-Host "$server does not have agent installed" -ForegroundColor Red
$server | Out-File -Append $notInstalled -ErrorAction SilentlyContinue
}
}
}
process-file1 -filename $liveSystems
Once I can get the systems to process through the list of installed and not installed I am trying to take the list of installed systems and check which ones have the service running and which ones do not.
$array = #()
foreach($i in (gc $installed)) {
$svc = Get-Service my_service -ComputerName $i -ea "0"
$obj = New-Object psobject -Property #{
Name = $svc.name
Status = $svc.status
Computer = $i
}
$array += $obj
}
$array | Select Computer,Name,Status | Export-Csv -Path $resultsFile -
NoTypeInformation
Last but not least I run through that list of running and not running and attempt to start the service on systems that are not running.
function process-CSVfile2 {
param($filename)
Import-Csv $filename |
ForEach-Object -PipelineVariable object {
if($_.Status -eq "Running") {
Write-Host "Your Service is currently Running on" $_.Computer
}
if($_.Status -eq "Stopped") {
$serviceName = 'my_service'
$service = Get-CimInstance Win32_Service -ComputerName $_.Computer -Filter "Name=$serviceName"
$service.Start()
$service.WaitForStatus("Started",'00:00:30')
Start-Sleep 10
}
}
}
Several of these blocks run separately but when put together they will not run. I can't seem to get past the second block where it just looks at the same line over and over.
In addition there is a piece I have been trying to get working that would install the application on systems that do not have the service installed but that is not working either but I will save that for a different time.
If anyone can help me with this I would really appreciate it. After 3 days of trying to get it running I am at my wits end.
I'd create objects and properties instead of files with computers online etc...
Something like:
$Computers=New-Object -TypeName System.Collections.ArrayList
$Servers = #(Get-Content -path c:\servers.txt)
$Servers = $Servers | ? {$_} | select-object -uniqe |ForEach-Object {$_.TrimEnd()}
$Servers|ForEach-Object {
$tempobj=New-Object -TypeName PSObject
$tempobj | Add-Member -type NoteProperty -name Name -value $_
$tempobj | Add-Member -type NoteProperty -name isOnline -value $FALSE
$tempobj | Add-Member -type NoteProperty -name Installed -value $FALSE
$tempobj | Add-Member -type NoteProperty -name serviceRunning -value $FALSE
[void]$Computers.Add($tempobj)
then You could work on array (no need for additional files)
$Computers|Where-Object {$_.isOnline -eq $TRUE}
etc

scriptblock returning object for compare-object

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

How to output from a Powershell hashtable to a output file

I am trying to get the .NetFramwork version from all the windows servers. I am using powershell script. I can get the output displayed but unable to get the output from the hashtable to a output file. Also how would I get rid of the "..." from VersionDetails : {1.0.3705, 1.1.4322, 2.0.50727, 3.0...} and show the full content.
Any help will be greatly appreciated
here is the code I am using:
$username = "username"
$password = "Password"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
$query = "select name from win32_directory where name like 'c:\\windows\\microsoft.net\\framework\\v%'"
$ComputerNames = Get-Content "d:\Scripts\serverList.txt"
foreach ($ComputerName in $ComputerNames)
{
write-host "ComputerName = $ComputerName"
$ComputerName | ForEach-Object {
$res = Get-WmiObject -query $query -Credential $cred -ComputerName $ComputerName | ForEach-Object {
Split-Path $_.name -Leaf } | # returns directories
Where-Object { $_ -like 'v*' } | # only include those that start with v
ForEach-Object { [system.version]( $_ -replace "^v" ) }
# remove "v" from the string and convert to version object
# Create hashtable with computername and version details
$prop = #{
ComputerName = $ComputerName
#V1_Present = &{ if ( $res | Where-Object { $_.Major -eq 1 -and $_.Minor -eq 0 } ) { $true } }
#V1_1Present = &{ if ( $res | Where-Object { $_.Major -eq 1 -and $_.Minor -eq 1 } ) { $true } }
V2_Present = &{ if ( $res | Where-Object { $_.Major -eq 2 -and $_.Minor -eq 0 } ) { $true } }
V3_Present = &{ if ( $res | Where-Object { $_.Major -eq 3 -and $_.Minor -eq 0 } ) { $true } }
V3_5Present = &{ if ( $res | Where-Object { $_.Major -eq 3 -and $_.Minor -eq 5 } ) { $true } }
V4_Present = &{ if ( $res | Where-Object { $_.Major -eq 4 -and $_.Minor -eq 0 } ) { $true } }
VersionDetails = $res
}
# Create and output PSobject using hashtable
New-Object PSObject -Property $prop
}
=========================================================
Output dispalys
PS D:\Scripts> .\GetDotNetFrameworkver.ps1
in for loop ComputerName = XXXXXXX
V4_Present : True
V3_5Present : True
V2_Present : True
V3_Present : True
ComputerName : XXXXX
VersionDetails : {1.0.3705, 1.1.4322, 2.0.50727, 3.0...}
Based on the answer of link there is a "simpler" (and faster) solution to fetch the versions.
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version,Release -ErrorAction Ignore | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select PSChildName, Version, Release
If you want to get the versions of different remote machines you can use PowerShell remoting. Be aware that you've to enable PS remoting .If your OS version is WIN10/WIN2012R2 it is enabled per default. If you're using an older OS you've to call Enable-PSRemoting on the remote machine. See this link for details.
Example:
$result = Invoke-Command -ComputerName computer1.domain, computer1.domain -Credential (Get-Credential ) -ScriptBlock {
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version,Release -ErrorAction Ignore | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select PSChildName, Version, Release
}
$hash = $result | group PSComputerName -AsHashTable # Group the .Net versions by computername
$hash.'computer1.domain' # Print all .Net version of computer1
$hash.'computer1.domain'.Version # Only print the version
Hope that helps.

Powershell command to command/script to check how many active users (local & domain) are currently logged in

I am looking for command/script to check how many users (local & domain) are currently logged in and have been active within the last 30 minutes so that I can decide to go for machine reboot or not.
Hopefully without Get-ADUser.
I came across two scripts but on executing they are returning only my information where on server I can see more than 5 user are logged in.
Script1
function Get-LoggedOnUser {
#Requires -Version 2.0
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[String[]]$ComputerName
)#End Param
Begin
{
Write-Host "`n Checking Users . . . "
$i = 0
}#Begin
Process
{
$ComputerName | Foreach-object {
$Computer = $_
try
{
$processinfo = #(Get-WmiObject -class win32_process -ComputerName $Computer -EA "Stop")
if ($processinfo)
{
$processinfo | Foreach-Object {$_.GetOwner().User} |
Where-Object {$_ -ne "NETWORK SERVICE" -and $_ -ne "LOCAL SERVICE" -and $_ -ne "SYSTEM"} |
Sort-Object -Unique |
ForEach-Object { New-Object psobject -Property #{Computer=$Computer;LoggedOn=$_} } |
Select-Object Computer,LoggedOn
}#If
}
catch
{
"Cannot find any processes running on $computer" | Out-Host
}
}#Forech-object(ComputerName)
}#Process
End
{
}#End
}#Get-LoggedOnUser
Script2
$regexa = '.+Domain="(.+)",Name="(.+)"$';
$regexd = '.+LogonId="(\d+)"$';
$logontype = #{"0"="Local System";
"2"="Interactive";
"3"="Network";
"4"="Batch";
"5"="Service";
"7"="Unlock";
"8"="NetworkCleartext";
"9"="NewCredentials";
"10"="RemoteInteractive";
"11"="CachedInteractive";
};
$logon_sessions = #(gwmi win32_logonsession);
$logon_users = #(gwmi win32_loggedonuser);
$session_user = #{};
$logon_users |% {$_.antecedent -match $regexa > $nul;
$username = $matches[1] + "\" + $matches[2];
$_.dependent -match $regexd > $nul;
$session = $matches[1];
$session_user[$session] += $username};
$logon_sessions |%{$starttime = [management.managementdatetimeconverter]::todatetime($_.starttime);
$loggedonuser = New-Object -TypeName psobject;
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Session" -Value $_.logonid;
$loggedonuser | Add-Member -MemberType NoteProperty -Name "User" -Value $session_user[$_.logonid];
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Type" -Value $logontype[$_.logontype.tostring()];
$loggedonuser | Add-Member -MemberType NoteProperty -Name "Auth" -Value $_.authenticationpackage;
$loggedonuser | Add-Member -MemberType NoteProperty -Name "StartTime" -Value $starttime;
$loggedonuser}
Help is appreciated.
Thanks,
Sambhav
I believe you cannot get information about other user sessions if not running PowerShell as administrator.

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