PowerShell Start-Job Increments - powershell

I am curious as to why Start-Job increments in twos. My worry is that I am doing something wrong that makes the ID of a new job jump by 2.
Start-Job -ScriptBlock {Get-WinEvent -LogName system -MaxEvents 1000}
Results as shown by Get-Job
Id Name State HasMoreData Command
-- ---- ----- ----------- -------
2 Job2 Completed False Get-WinEvent -LogName system -MaxEvents 1000
4 Job4 Completed False Get-WinEvent -LogName system -MaxEvents 1000
6 Job6 Completed True Get-WinEvent -LogName system -MaxEvents 1000
Question: Can you control the Start-Job Id increments, or force them to be just 1?

Each time you start a job, it consists of a parent job and one or more child jobs. If you run get-job | fl you'll see the child jobs, and you'll see that their names are the "missing" odd numbered names.

#1.618 give the right answer, here are some more details :
Start-Job -ScriptBlock {Get-Process}
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
2 Job2 BackgroundJob Running True localhost Get-Process
Get-Job | fl *
State : Completed
HasMoreData : True
StatusMessage :
Location : localhost
Command : Get-Process
JobStateInfo : Completed
Finished : System.Threading.ManualResetEvent
InstanceId : 49a67ca4-840b-49ec-b293-efa9303e38bb
Id : 2
Name : Job2
ChildJobs : {Job3}
PSBeginTime : 03/03/2014 20:43:54
PSEndTime : 03/03/2014 20:44:00
PSJobTypeName : BackgroundJob
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
get-job -IncludeChildJob
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
2 Job2 BackgroundJob Completed True localhost Get-Process
3 Job3 Completed True localhost Get-Process
Here is why, when you start a job, powershell create two jobs ?
Windows PowerShell jobs created through Start-Job always consist of a parent job and a child job. The child job does the actual work. If you were running the job against a number of remote machines by using Invoke-Command and its –AsJob parameter, you would get one child job per remote machine.
When you manage jobs, anything you do to the parent job is automatically applied to any child jobs. Removing or stopping the parent job performs the same action on the child jobs. Getting the results of the parent job means you get the results of all the child jobs.
You can access the child jobs directly to retrieve their data, n a simple job, as in the example, you can access the data through the parent or child jobs :
Receive-Job -Id 2 -Keep
Receive-Job -Id 3 -Keep
When you have multiple child jobs, its usually easier to access the child jobs in turn:
$jobs = Get-Job -Name Job2 | select -ExpandProperty ChildJobs
foreach ($job in $jobs){Receive-Job -Job $job -Keep}

Related

How can I capture the Information output stream of a Job?

If I start a job, and that job outputs messages to the Information stream (or any stream for that matter), how can I capture that into a variable, or simply output it after I receive the job?
$script = {
[PSCustomObject]#{ "CoolProperty" = "CoolValue" }
Write-Information "Returning a cool object" -InformationAction "Continue"
}
Start-Job -Name "test" -ScriptBlock $script | Out-Null
Wait-Job -Name "test" | Out-Null
$result = Receive-Job -Name "test" 6>&1
Remove-Job -Name "test"
The above code will output
Returning a cool object
to the console, but I would like to capture that and output it to the log file I'm using for the overall script, e.g.:
$JustTheInfoStreamFromJob | Out-File "c:\file.log" -Append
I don't want to log the captured $result, because it also contains the Output stream (i.e. the object). I only want to log the Information stream. So I'm looking for a way to separate that out.
I see that there is an -InformationVariable parameter, but I don't understand how to use it, or if it's relevant for my question. I've tried a few methods of redirection, but I have very little idea what I'm doing when it comes to streams.
This question gives me some hints, but not enough to understand.
This question is very similar but appears to have been asked before the Information stream existed. I'd rather not use Transcripts unless it's necessary; I feel like there should be a better way.
Some answers reference the ChildJobs, Output, and Information properties of a Job object, but I'm having a hard time understanding how to use them, as they are always null in my simple tests.
Thanks for your time.
Solved
Thanks to #Santiago Squarzon and #Steven for two different working solutions:
Remove-Job "test"
$script = {
[PSCustomObject]#{ "CoolProperty" = "CoolValue" }
Write-Information "Returning a cool object"
}
Start-Job -Name "test" -ScriptBlock $script | Out-Null
$job = Get-Job -Name "test" -IncludeChildJob # Steven's solution
$job | Wait-Job | Out-Null
$info = $job.Information # Steven's solution
$result = Receive-Job -Name "test" -InformationVariable info2 # Santiago Squarzon's solution
Remove-Job -Name "test"
$result
"---"
$info
"---"
$info2
This allows me to capture the job's output and the job's Info stream separately (two different ways):
CoolProperty RunspaceId
------------ ----------
CoolValue f49c78bd-eda3-4c47-a6bc-d89a146618e9
---
Returning a cool object
---
Returning a cool object
The different Streams are stored separately in the job object:
State : Completed
HasMoreData : True
StatusMessage :
Location : localhost
Command : Write-Information "something"
JobStateInfo : Completed
Finished : System.Threading.ManualResetEvent
InstanceId : ff5d1155-aca1-40fa-8e4e-dce6b87c709c
Id : 2
Name : test
ChildJobs : {Job3}
PSBeginTime : 4/2/2021 9:41:26 PM
PSEndTime : 4/2/2021 9:41:26 PM
PSJobTypeName : BackgroundJob
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
Information : {}
Notice the child Job, but you can get at the information stream with something like:
(Get-Job -Name test -IncludeChildJob).Information
In the above example it will return "something", reference the command from the job.
Reference here there's some other good information as well.

Powershell - Getting History of Task Scheduler Run Results

I have a Powershell script as follows:
if (something)
{
# do something
# will return 0 on success
}
else
{
exit 12345
}
I want to be able to check that there has been at least one success (return code 0) in the last 24 hours (the script will return 12345 more than 0)
In another script I have this code:
$events = #(
Get-WinEvent -FilterXml #'
<QueryList>
<Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
<Select Path="Microsoft-Windows-TaskScheduler/Operational">
*[EventData/Data[#Name='taskname']='\My Test']
</Select>
</Query>
</QueryList>
'# -ErrorAction Stop
$events | Where-Object {$_.ID -eq 102} | Select-Object *
which shows me the history of the task runs but I cannot find how I get the Run Result from here.
I can interrogate a single history item as follows:
$a = $events | Where-Object {$_.ID -eq 102} | Select-Object *
$a[0] | Get-Member
which returns
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
ActivityId NoteProperty guid ActivityId=d6ac8489-c0e1-4dbd-b06e-7ecefaf1c20c
Bookmark NoteProperty EventBookmark Bookmark=System.Diagnostics.Eventing.Reader.EventBookmark
ContainerLog NoteProperty string ContainerLog=Microsoft-Windows-TaskScheduler/Operational
Id NoteProperty int Id=102
Keywords NoteProperty long Keywords=-9223372036854775807
KeywordsDisplayNames NoteProperty ReadOnlyCollection[string] KeywordsDisplayNames=System.Collections.ObjectModel.ReadOnlyCollection`1[System.String]
Level NoteProperty byte Level=4
LevelDisplayName NoteProperty string LevelDisplayName=Information
LogName NoteProperty string LogName=Microsoft-Windows-TaskScheduler/Operational
MachineName NoteProperty string MachineName=MyPC.mydomain
MatchedQueryIds NoteProperty uint32[] MatchedQueryIds=System.UInt32[]
Message NoteProperty string Message=Task Scheduler successfully finished "{d6ac8489-c0e1-4dbd-b06e-7ecefaf1c20c}" instance of the "\My Test" task for user "MyD...
Opcode NoteProperty int16 Opcode=2
OpcodeDisplayName NoteProperty string OpcodeDisplayName=Stop
ProcessId NoteProperty int ProcessId=2544
Properties NoteProperty List[EventProperty] Properties=System.Collections.Generic.List`1[System.Diagnostics.Eventing.Reader.EventProperty]
ProviderId NoteProperty guid ProviderId=de7b24ea-73c8-4a09-985d-5bdadcfa9017
ProviderName NoteProperty string ProviderName=Microsoft-Windows-TaskScheduler
Qualifiers NoteProperty object Qualifiers=null
RecordId NoteProperty long RecordId=21093
RelatedActivityId NoteProperty object RelatedActivityId=null
Task NoteProperty int Task=102
TaskDisplayName NoteProperty string TaskDisplayName=Task completed
ThreadId NoteProperty int ThreadId=14152
TimeCreated NoteProperty datetime TimeCreated=16/11/2020 13:26:20
UserId NoteProperty SecurityIdentifier UserId=S-1-5-18
Version NoteProperty byte Version=0
But I cannot find the info I need in any of the properties. I would expect it is stored somewhere in hex format (0x3039)
Did you tried to find the output code in the XML-Output of the event?
foreach ($e in $events){
[xml]$eXmls = $e.ToXml()
$eXmls.event.EventData
}
You have what you need. You just need to expose it for viewing/capture, etc.
The example below is using the pristine, Windows Sandbox, configured logging, creating a simple task, running it once, and grabbing the results.
wevtutil set-log Microsoft-Windows-TaskScheduler/Operational /enabled:true
wevtutil get-log Microsoft-Windows-TaskScheduler/Operational
Get-WinEvent -ListLog * |
Where-Object -Property logname -match task
# Results
<#
LogMode MaximumSizeInBytes RecordCount LogName
------- ------------------ ----------- -------
Circular 10485760 37 Microsoft-Windows-TaskScheduler/Operational
Circular 1052672 8 Microsoft-Windows-TaskScheduler/Maintenance
Circular 1052672 0 Microsoft-Windows-Shell-Core/LogonTasksChannel
Circular 1052672 0 Microsoft-Windows-Mobile-Broadband-Experience-Parser-Task/Operational
Circular 1052672 0 Microsoft-Windows-BackgroundTaskInfrastructure/Operational
#>
$XmlQuery = #'
<QueryList>
<Query Id="0" Path="Microsoft-Windows-TaskScheduler/Operational">
<Select Path="Microsoft-Windows-TaskScheduler/Operational">
*[EventData/Data[#Name='taskname']='\TestTask']
</Select>
</Query>
</QueryList>
'#
Get-WinEvent -FilterXml $XmlQuery
# Results
<#
ProviderName: Microsoft-Windows-TaskScheduler
TimeCreated Id LevelDisplayName Message
----------- -- ---------------- -------
11/16/2020 2:52:16 PM 102 Information Task Scheduler successfully finished "{ca247629-6342-4e3d-9848-af234f84ae0c}" instance of the "\TestTask" task for user "F2B00BB4-0260...
11/16/2020 2:52:16 PM 201 Information Task Scheduler successfully completed task "\TestTask" , instance "{ca247629-6342-4e3d-9848-af234f84ae0c}" , action "C:\Windows\System...
11/16/2020 2:52:08 PM 110 Information Task Scheduler launched "{ca247629-6342-4e3d-9848-af234f84ae0c}" instance of task "\TestTask" for user "WDAGUtilityAccount" .
11/16/2020 2:52:08 PM 200 Information Task Scheduler launched action "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.EXE" in instance "{ca247629-6342-4e3d-9848-af234...
11/16/2020 2:52:08 PM 100 Information Task Scheduler started "{ca247629-6342-4e3d-9848-af234f84ae0c}" instance of the "\TestTask" task for user "F2B00BB4-0260-4\WDAGUtility...
11/16/2020 2:52:08 PM 129 Information Task Scheduler launch task "\TestTask" , instance "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.EXE" with process ID 5520.
11/16/2020 2:52:04 PM 106 Information User "F2B00BB4-0260-4\WDAGUtilityAccount" registered Task Scheduler task "\TestTask
#>
($events = #(
Get-WinEvent -FilterXml $XmlQuery -ErrorAction Stop
)) |
Where-Object {$PSItem.ID -eq 106} |
Select-Object -Property '*' -First 1 |
Format-List -Force
<#
Message : User "F2B00BB4-0260-4\WDAGUtilityAccount" registered Task Scheduler task "\TestTask"
Id : 106
Version : 0
Qualifiers :
Level : 4
Task : 106
Opcode : 0
Keywords : -9223372036854775808
RecordId : 1
ProviderName : Microsoft-Windows-TaskScheduler
ProviderId : de7b24ea-73c8-4a09-985d-5bdadcfa9017
LogName : Microsoft-Windows-TaskScheduler/Operational
ProcessId : 960
ThreadId : 1440
MachineName : f2b00bb4-0260-425b-b5d3-7b0331e05b80
UserId : S-1-5-18
TimeCreated : 11/16/2020 2:52:04 PM
ActivityId :
RelatedActivityId :
ContainerLog : Microsoft-Windows-TaskScheduler/Operational
MatchedQueryIds : {}
Bookmark : System.Diagnostics.Eventing.Reader.EventBookmark
LevelDisplayName : Information
OpcodeDisplayName : Info
TaskDisplayName : Task registered
KeywordsDisplayNames : {}
Properties : {System.Diagnostics.Eventing.Reader.EventProperty, System.Diagnostics.Eventing.Reader.EventProperty}
#>
(($events = #(
Get-WinEvent -FilterXml $XmlQuery -ErrorAction Stop
)) |
Where-Object {$PSItem.ID -eq 106} |
Select-Object -Property '*' -First 1).Message
# Results
<#
User "F2B00BB4-0260-4\WDAGUtilityAccount" registered Task Scheduler task "\TestTask"
#>
(($events = #(
Get-WinEvent -FilterXml $XmlQuery -ErrorAction Stop
)) |
Where-Object {$PSItem.ID -eq 106} |
Select-Object -Property '*' -First 1).Opcode
# Results
<#
0
#>
# Code Reference
<#
Op Codes Description
________ ____________
0 or 0x0 The operation completed successfully.
1 or 0x1 Incorrect function called or unknown function called.
2 or 0x2 File not found.
10 or 0xa The environment is incorrect.
0x41300 Task is ready to run at its next scheduled time.
0x41301 Task is currently running.
0x41302 Task is disabled.
0x41303 Task has not yet run.
0x41304 There are no more runs scheduled for this task.
0x41306 Task is terminated.
0x8004131F An instance of this task is already running.
0x800704DD The service is not available (is ‘Run only when a user is logged on’ checked?)
0xC000013A The application terminated as a result of a CTRL+C.
0xC06D007E Unknown software exception.
#>

how to format Powershell output in specific way?

I need to scan my network for specific processes on servers. I've done this script:
28..31 | ForEach-Object { Get-Process -ComputerName "192.168.0.$_" -Name svc* }
Now how can I format output so it shows on which IP address found process shown? Thank you.
I suggest switching to PowerShell's remoting, because:
it provides a framework for executing any command remotely - rather than relying on individual cmdlets to support a -ComputerName parameter and uses a firewall-friendly transport.
it will continue to be supported in PowerShell [Core] v6+, where the cmdlet-individual -ComputerName parameters aren't supported anymore; this obsolete remoting method uses an older, less firewall-friendly form of remoting that the - obsolete since v3 - WMI cmdlets also use (the latter were replaced by the CIM cmdlets).
It is therefore better to consistently use the firewall-friendly PowerShell remoting with its generic remoting commands (Invoke-Command, Enter-PSSession, ...).
If you use Invoke-Command to target (multiple) remote computers, the objects returned automatically contain and display the name of the originating computer, via the automatically added .PSComputerName property:
# Create the array of IP addresses to target:
$ips = 28..31 -replace '^', '192.168.0.'
# Use a *single* Invoke-Command call to target *all* computers at once.
# Note: The results will typically *not* reflect the input order of
# given IPs / computer names.
Invoke-Command -ComputerName $ips { Get-Process -Name svc* }
You'll see output such as the following - note the PSComputerName column:
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName PSComputerName
------- ------ ----- ----- ------ -- -- ----------- --------------
1013 18 7464 13732 52.72 8 0 svchost 192.168.0.30
...
Note that you can suppress automatic display of the .PSComputerName property with Invoke-Command's -HideComputerName parameter.
However, the property is still available programmatically, which allows you to do the following:
Invoke-Command -ComputerName $ips { Get-Process -Name svc* } -HideComputerName |
Format-Table -GroupBy PSComputerName
This yields display output grouped by computer name / IP:
PSComputerName: 192.168.0.30
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1013 18 7464 13732 52.72 8 0 svchost
...
PSComputerName: 192.168.0.28
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1007 17 7112 12632 65.45 11 0 svchost
...
If you wanted to sort by IP address before grouping, you could insert | Sort-Object { [version] $_.PSComputerName }[1] before the Format-Table call.
For sorting by computer names, just
| Sort-Object PSComputerName would do.
[1] Casting to [version] is a trick to ensure proper sorting of IP addresses; IP address strings can be interpreted as [version] (System.Version) instances, and such instances are directly comparable, using the numeric values of the version components (first by .MajorVersion, then by .MinorVersion, ...)
here's one way to do the job. [grin] what it does ...
builds the ip final octet range
sets the IPv4 base octets
builds the list of processes to search for
sets the "no response" text
iterates thru the 4th octet range
builds the IPv4 address
checks to see if it is online/responding
if so, gets the hostname
for my version of PoSh [win7, ps5.1] the Get-Process cmdlet will NOT accept an ip address. a hostname is required.
corrects for the damaged hostname returned when one uses ATT for inet service
creates an ordered hashtable to use for building the property list
builds the various props as needed
converts the hashtable to a PSCustomObject
sends that to the $Results collection
shows it on screen
sends it to a CSV file
here's the code ...
$4thOctetRange = 64..66
$IpBase = '192.168.1'
$ProcessList = #(
'AutoHotKey'
'BetterNotBeThere'
'DisplayFusion'
'Foobar2000'
)
$NoResponse = '__n/a__'
$Results = foreach ($4OR_Item in $4thOctetRange)
{
$Ip = '{0}.{1}' -f $IpBase, $4OR_Item
$Online = Test-Connection -ComputerName $Ip -Count 1 -Quiet
if ($Online)
{
# getting the hostname is REQUIRED by my version of Get-Process
# it will not accept an IP address
# version info = win7, ps5.1
# this may need adjusting for your returned hostname
# mine shows Hostname.attlocal.net
# that is not valid with any query i make, so i removed all after the 1st dot
$HostName = ([System.Net.Dns]::GetHostByAddress($Ip)).HostName.Split('.')[0]
}
else
{
$HostName = $NoResponse
}
$TempProps = [ordered]#{}
$TempProps.Add('IpAddress', $Ip)
$TempProps.Add('Online', $Online)
$TempProps.Add('HostName', $HostName)
foreach ($PL_Item in $ProcessList)
{
if ($TempProps['Online'])
{
# if the process aint found, the "SilentlyContinue" forces a $Null
# the "[bool]" then coerces that to a "False"
$TempProps.Add($PL_Item, [bool](Get-Process -ComputerName $HostName -Name $PL_Item -ErrorAction SilentlyContinue))
}
else
{
$TempProps.Add($PL_Item, $NoResponse)
}
}
# send the object out to the $Results collection
[PSCustomObject]$TempProps
}
# send to screen
$Results
# send to CSV file
$Results |
Export-Csv -LiteralPath "$env:TEMP\Senator14_RemoteProcessFinder.csv" -NoTypeInformation
truncated screen output ...
IpAddress : 192.168.1.65
Online : True
HostName : ZK_01
AutoHotKey : True
BetterNotBeThere : False
DisplayFusion : True
Foobar2000 : True
IpAddress : 192.168.1.66
Online : False
HostName : __n/a__
AutoHotKey : __n/a__
BetterNotBeThere : __n/a__
DisplayFusion : __n/a__
Foobar2000 : __n/a__
csv file content ...
"IpAddress","Online","HostName","AutoHotKey","BetterNotBeThere","DisplayFusion","Foobar2000"
"192.168.1.64","False","__n/a__","__n/a__","__n/a__","__n/a__","__n/a__"
"192.168.1.65","True","ZK_01","True","False","True","True"
"192.168.1.66","False","__n/a__","__n/a__","__n/a__","__n/a__","__n/a__"

Can somebody explain why the following tiny powershell script behaves as it does?

Please, observe the following small powershell script (let us call it a.ps1):
param([switch]$WithFormat)
[System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe")
"-1-"
if ($WithFormat)
{
#{a=$null} | Format-Table
}
"-2-"
$j = Start-Job { [System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe") }
"-3-"
Wait-Job $j
"-4-"
Receive-Job $j
"-5-"
Remove-Job $j
Now, allow me to run it once without any arguments and then with the -WithFormat switch:
PS C:\tmp\1> .\a.ps1
GAC Version Location
--- ------- --------
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
-1-
-2-
-3-
HasMoreData : True
StatusMessage :
Location : localhost
Command : [System.Reflection.Assembly]::LoadFile("C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe")
JobStateInfo : Completed
Finished : System.Threading.ManualResetEvent
InstanceId : b70f261c-50f7-455f-9169-f200df78dc9d
Id : 168
Name : Job168
ChildJobs : {Job169}
PSBeginTime : 3/28/2015 10:32:10 PM
PSEndTime : 3/28/2015 10:32:12 PM
PSJobTypeName : BackgroundJob
Output : {}
Error : {}
Progress : {}
Verbose : {}
Debug : {}
Warning : {}
State : Completed
-4-
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
-5-
PS C:\tmp\1> .\a.ps1 -WithFormat
GAC Version Location
--- ------- --------
True v4.0.30319 C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
-1-
Name Value
---- -----
a
-2-
-3-
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
170 Job170 BackgroundJob Completed True localhost [System.Reflection.As...
-4-
RunspaceId : e1a6cac3-80f8-4dd8-8f8b-f687d1dcc8a0
CodeBase : file:///C:/Windows/Microsoft.Net/assembly/GAC_64/MSBuild/v4.0_4.0.0.0__b03f5f7f11d50a3a/MSBuild.exe
FullName : MSBuild, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
EntryPoint : Int32 Main()
DefinedTypes : {Microsoft.Build.Shared.AssemblyNameComparer, Microsoft.Build.Shared.CollectionHelpers, Microsoft.Build.Shared.DirectoryGetFiles, Microsoft.Build.Shared.GetDirectories...}
Evidence : {<System.Security.Policy.GacInstalled version="1"/>
, <StrongName version="1"
Key="002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E8
21C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293"
Name="MSBuild"
Version="4.0.0.0"/>
, <System.Security.Policy.Url version="1">
<Url>file:///C:/Windows/Microsoft.Net/assembly/GAC_64/MSBuild/v4.0_4.0.0.0__b03f5f7f11d50a3a/MSBuild.exe</Url>
</System.Security.Policy.Url>
, <System.Security.Policy.Zone version="1">
<Zone>MyComputer</Zone>
</System.Security.Policy.Zone>
...}
PermissionSet : {}
SecurityRuleSet : Level2
ManifestModule : MSBuild.exe
ReflectionOnly : False
Location : C:\Windows\Microsoft.Net\assembly\GAC_64\MSBuild\v4.0_4.0.0.0__b03f5f7f11d50a3a\MSBuild.exe
ImageRuntimeVersion : v4.0.30319
GlobalAssemblyCache : True
HostContext : 0
IsDynamic : False
EscapedCodeBase : file:///C:/Windows/Microsoft.Net/assembly/GAC_64/MSBuild/v4.0_4.0.0.0__b03f5f7f11d50a3a/MSBuild.exe
ExportedTypes : {Microsoft.Build.CommandLine.MSBuildApp, Microsoft.Build.CommandLine.MSBuildApp+ExitType}
IsFullyTrusted : True
CustomAttributes : {[System.Diagnostics.DebuggableAttribute((System.Diagnostics.DebuggableAttribute+DebuggingModes)2)], [System.Runtime.CompilerServices.ExtensionAttribute()],
[System.Runtime.CompilerServices.InternalsVisibleToAttribute("MSBuild.Whidbey.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c8
34c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")],
[System.Runtime.CompilerServices.InternalsVisibleToAttribute("MSBuild.Unittest, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921
eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]...}
Modules : {MSBuild.exe}
-5-
PS C:\tmp\1>
For the life of me, I do not understand why the two runs produce such a drastically different output!
Any ideas?
EDIT
Guys, notice the different output between the lines -4- and -5-. That output corresponds to the invocation of [System.Reflection.Assembly]::LoadFile in the context of a background job and indicates that when I call the script with the -WithFormat flag a different security policy is used to load the assembly in the background job. That is the essence of the question - why on Earth would the use of Format-Table do that ?
EDIT2
Looks like there is a bug, but much more innocent and having nothing to do with the security policies. Powershell's notion of the default object rendering gets screwed up by calling Format-Table in my example.
EDIT3
Tried another script:
param([switch]$WithFormat,[switch]$WithJob)
$ScriptBlock = { dir "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" }
&$ScriptBlock
"-1-"
if ($WithFormat)
{
#{a=$null} | Format-Table
}
"-2-"
if ($WithJob)
{
$j = Start-Job $ScriptBlock
"-3-"
Wait-Job $j
"-4-"
Receive-Job $j
"-5-"
Remove-Job $j
}
else
{
&$ScriptBlock
}
Running it with the different set of arguments reveals to me that there is a real mess with the default object formatting.
P.S.
PS C:\tmp\1> $PSVersionTable
Name Value
---- -----
PSVersion 3.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.34209
BuildVersion 6.2.9200.17065
PSCompatibleVersions {1.0, 2.0, 3.0}
PSRemotingProtocolVersion 2.2
PS C:\tmp\1> [System.Environment]::OSVersion.Version
Major Minor Build Revision
----- ----- ----- --------
6 2 9200 0
PS C:\tmp\1>
I use Out-String with Format-* cmdlets to avoid such differences in mixed output, i.e. in your case #{a=$null} | Format-Table | Out-String. The answer on the Why part is difficult, it's about internal details of PowerShell formatters.
This has to do with how PowerShell formats output when you have multiple object types in your output stream. It has nothing to do with jobs, assemblies, or security policy. PowerShell formats output by trying to display the same properties for all the objects in the output stream in a table. If you start mixing objects with different properties however, it just starts writing those objects properties in a list format. I don't know why, but PowerShell sort of "resets" when it hits a Format-Table and starts with next object as the current object type for formatting output. Perhaps this is a bug, I don't know.
We can demonstrate the behavior with a simpler example. Consider this input
gps idle; gsv winrm; gps idle
gives this output
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
0 0 0 4 0 0 Idle
Status : Stopped
Name : winrm
DisplayName : Windows Remote Management (WS-Management)
0 0 0 4 0 0 Idle
and this input
gps idle; #{a=0} | ft; gsv winrm; gps idle
gives this output
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
0 0 0 4 0 0 Idle
Name Value
---- -----
a 0
Status Name DisplayName
------ ---- -----------
Stopped winrm Windows Remote Management (WS-Manag...
Id : 0
Handles : 0
CPU :
Name : Idle
The output is very different between these two even though the only difference is an additional call to Format-Table.
In the first case PowerShell see a Process and starts formatting output for that. It gets a ServiceController object which does not have the same properties so it outputs it as a list. Then it gets another process object and continues the table (note how there are no headers).
In the second case, calling Format-Table seems to "reset" the current object PowerShell is considering as the output. When it receives the second Process object, it does not have the same properties as the ServiceController so it just displays it in a list format.
Blasting everything to Out-String or Format-Table will certainly work if your goal is format the output in a certain way, but that might not be solution if you want to be able to work with output as objects.

Unexpected behavior with Get-Job when only one job running

I'm new to Powershell and I'm having an issue with the Get-Job command. In my script, I'm testing out multi-threading and am doing something like:
$Program = {
"Thread " + $args[0];
Start-Sleep 5;
}
Start-Job $Program -ArgumentList #($i) | Out-Null
The Start-Job call is actually in a loop in which I'm creating multiple jobs. Below this, I have:
Get-Job
"Jobs Running: " + $(Get-Job -State Running).count
If there are multiple jobs running, I will get output like:
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
2201 Job2201 Running True localhost ...
2199 Job2199 Running True localhost ...
2197 Job2197 Running True localhost ...
2195 Job2195 Running True localhost ...
2193 Job2193 Completed True localhost ...
2191 Job2191 Completed True localhost ...
2189 Job2189 Completed True localhost ...
2187 Job2187 Completed True localhost ...
Jobs Running: 4
But if there is only one job running, it seems that $(Get-Job -State Running).count isn't returning anything:
Id Name State HasMoreData Location Command
-- ---- ----- ----------- -------- -------
2207 Job2207 Running True localhost ...
Jobs Running:
As you can see, there is one job running, but $(Get-Job -State Running).count doesn't return anything. Any idea what's happening here? To me, it looks like that if there are multiple jobs, $(Get-Job -State Running) returns a collection of jobs which has the .count property, whereas if there is only one job, it returns just that job, and doesn't have the .count property. If this is the case (or if I'm doing something wrong), what command should I be using to get my expected result of $(Get-Job -State Running).count == 1?
Try using Measure-Object
$(Get-Job -State Running | Measure-Object).count
In PS 2.0 count only works on arrays. When Get-Job only returns one job, it returns it as an OBJECT, not an array. To make it work, you could e.g. force Get-Job to always return an array by using #(code) . Try this:
$Program = {
"Thread " + $args[0];
Start-Sleep 5;
}
Start-Job $Program -ArgumentList #($i) | Out-Null
Get-Job
"Jobs Running: " + $(#(Get-Job -State Running).count)