header1   header
header
header Register : : Login header
header
connector   connector
menuleft menuright
submenu   submenu
left
Getting Output From Powershell Jobs Into Variable
Last Post 12 Aug 2010 10:56 AM by Scott Hendricks. 8 Replies.
Printer Friendly
  •  
  •  
  •  
  •  
  •  
Sort:
PrevPrev NextNext
You are not authorized to post a reply.
Author Messages Resolved
Scott HendricksUser is Offline
New Member
New Member
Posts:12
Avatar

--
11 Aug 2010 02:19 PM
    Hi,

    I am busy scripting a system in PowerShell that allows us to run groups of scripts against remote servers easily with little scripting knowledge from the end user. I am running into a problem where when using PS-Remoting and PS-Automation Jobs, I cannot capture the output and act on it in a variable.

    Here is a simple example of what is happening:
    PS C:\> $ScriptBlock = { write-host "hi" }
    PS C:\> $Job = Start-Job -ScriptBlock $ScriptBlock
    PS C:\> $JobOutput = Receive-Job -Job $Job
    hi
    PS C:\> $JobOutput -eq $null
    True

    I am trying to capture the output (in this case, the string "hi") and act on it in some way. But in this case the output is piped directly to the console and I am left with a null variable.

    I know about this workaround:
    PS C:\> $ScriptBlock = { powershell.exe -Command { write-host "hi" } }
    PS C:\> $Job = Start-Job -ScriptBlock $ScriptBlock
    PS C:\> $JobOutput = Receive-Job -Job $Job
    PS C:\> $JobOutput
    hi

    However if I do it that way I can only get output after the script finishes executing, which is not what I want. Some of the scripts run for many minutes and log a lot. I have no control over the contents of the scripts.

    Anyone have any suggestions? I thought of using tee-object or trying to use some other pipeline operations but I have had no success at all.

    Edit: I changed the title of the post to more reflect what I am trying to do.
    Mihail StacanovUser is Offline
    New Member
    New Member
    Posts:15
    Avatar

    --
    12 Aug 2010 03:28 AM
    #I've used this for ability to receive job's output. But here is one problem, if the job didn't complete I receive it's current output.

    $ScriptBlock = { write-host "Test before Sleep"; Start-Sleep 30; Write-Host "Test after Sleep" }
    $Job = Start-Job -ScriptBlock $ScriptBlock
    $JobID = [string](Get-Job | where {$_.command -eq $ScriptBlock} | select -First 1 | foreach {$_.id})
    Receive-Job -Id $JobID

    #When you create a job there are creating an ID. Using this ID you can find your desired job and takes its output

    #One thing is important that if you receive current job output the next one check will contain only the new added information. For example at run this script I will not receive any info. After running in a few seconds this command again Receive-Job -Id $JobID I will receive "Test before Sleep" text and if I will run Receive-Job -Id $JobID after a 30 seconds I will see only "Test after Sleep" text.

    "To prevent Receive-Job from deleting the job results that it has
        returned, use the Keep parameter. As a result, Receive-Job returns all
        of the results that have been generated until that time." get-help about_job



    http://proproit.com
    Scott HendricksUser is Offline
    New Member
    New Member
    Posts:12
    Avatar

    --
    12 Aug 2010 03:59 AM
    This does not store the output in a variable, like I need. Your script only writes the output to the console.

    Also, as a side note, you can remove the third line in your script block and then just do "Receive-Job -Job $Job". You dont need to get the Job ID to receive the job.
    Mihail StacanovUser is Offline
    New Member
    New Member
    Posts:15
    Avatar

    --
    12 Aug 2010 04:23 AM
    $joboutput = [string](Receive-Job -Job $Job -Keep)

    #ok make your job's output to string and it you will be able to put it to variable
    http://proproit.com
    Scott HendricksUser is Offline
    New Member
    New Member
    Posts:12
    Avatar

    --
    12 Aug 2010 06:43 AM
    I'm not quite sure you understand the problem I am facing. What you suggested above doesn't work. You are suggesting doing something like this:
    PS C:\> $ScriptBlock = { write-host "hi" }
    PS C:\> $Job = Start-Job -ScriptBlock $ScriptBlock
    PS C:\> $JobOutput = [String](Receive-Job -Job $Job -Keep)
    hi
    PS C:\> $JobOutput -eq ""
    True

    That still does not work. I need the output from the ScriptBlock in a variable, I do not want it written to the console. I have tried many different ways to get it but have come up with no solution so far.

    I have no control over the scripts that are run inside of the ScriptBlock. My guess as to solve this is to write a custom powershell host, however I wanted to avoid that and see what others thought before I went down that path.
    Mihail StacanovUser is Offline
    New Member
    New Member
    Posts:15
    Avatar

    --
    12 Aug 2010 07:03 AM
    #sorry, forgot to mention to use write-output instead write-host. as you said you do not need to output it to host :)
    #final commands are

    $ScriptBlock = { write-output "hi" }
    $Job = Start-Job -ScriptBlock $ScriptBlock
    $JobOutput = [String](Receive-Job -Job $Job -Keep)
    $JobOutput

    http://proproit.com
    Scott HendricksUser is Offline
    New Member
    New Member
    Posts:12
    Avatar

    --
    12 Aug 2010 07:04 AM
    Once again, I have no control over the scripts that are being run. These are signed scripts created over many months and years, and circling back and changing write-host to write-output is not a feasible option.
    Scott HendricksUser is Offline
    New Member
    New Member
    Posts:12
    Avatar

    --
    12 Aug 2010 07:41 AM
    Well, based on a thread a coworker showed me in the MVP mailing list, this is not possible with write-host. It is a limitation of the function itself. I do have an idea for a good workaround using some .NET classes that will actually stream output back, and I will post my solution here for anyone who wants it once I implement it. I know it will work because I've done something similar, I just need to figure out the details.


    Thanks all for your help!
    Scott HendricksUser is Offline
    New Member
    New Member
    Posts:12
    Avatar

    --
    12 Aug 2010 10:56 AM
    • Accepted Answer
    Here is the example script that will allow me to workaround this issue while still receiving output as it comes in. Thanks again to all who looked at this.

    Contents Of C:\Helper.ps1 :
    param
    (
    	[String] $PoshCommand
    )
    
    $ErrorActionPreference = "Stop"
    $Command               = "powershell.exe"
    $Arguments             = "-Command $PoshCommand"
    $ProcessStartInfo      = $null
    
    $ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo($Command, $Arguments)
    
    $ProcessStartInfo.UseShellExecute = $False
    $ProcessStartInfo.RedirectStandardOutput = $True
    $ProcessStartInfo.RedirectStandardError = $True
    $Process = [System.Diagnostics.Process]::Start($ProcessStartInfo)
    
    while (-not $Process.HasExited)
    {
    	Start-Sleep 1
    	$Process.Refresh();
    	$Output = $Process.StandardOutput.ReadToEnd()
    	if ($Output.Trim() -ne "") { $Output.Trim() }
    	$Output = $Process.StandardError.ReadToEnd()
    	if ($Output.Trim() -ne "") { $Output.Trim() }
    }
    
    $Process.Refresh();
    $Output = $Process.StandardOutput.ReadToEnd()
    if ($Output.Trim() -ne "") { $Output.Trim() }
    $Output = $Process.StandardError.ReadToEnd()
    if ($Output.Trim() -ne "") { $Output.Trim() }
    
    if ($Process.ExitCode -ne 0)
    {
    	throw "Process Exited With Error Code: $($Process.ExitCode)"
    }
    
    exit 0

    Calling Script:
    $Command = "C:\Helper.ps1 `"write-host ```"hi```"`""
    $ScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($Command)
    $Job = Invoke-Command -ScriptBlock $ScriptBlock -Session $(New-PSSession) -AsJob
    while ($Job.State -eq "Running")
    {
    	#You can call Receive-Job here to get output so far.
    	start-sleep -milliseconds 100
    }
    $Output = Receive-Job -Job $Job
    write-host "Output: $Output"

    Now I can get the output of a write-host command in a variable. It's a horribly complicated workaround (incidentally I couldn't get Start-Job to work), but it does the job. I am eventually going to put the helper script in a shared directory so I can call it on all my remote servers. I hope my solution helps others.
    You are not authorized to post a reply.


    Active Forums 4.3
    right
    footer   footer
    footer Sponsored by Quest Software • SAPIEN Technologies • Compellent • Microsoft Windows Server 2008 R2 footer
    footer   footer