PoshRSJob icon indicating copy to clipboard operation
PoshRSJob copied to clipboard

Start-RSJob not honouring current console width

Open scottbass opened this issue 7 years ago • 6 comments

Do you want to request a feature or report a bug? Bug? (might be considered an enhancement?)

What is the current behavior? If I run my script, the log file lines are not wrapped. If I call the script from Start-RSJob, the log file lines are wrapped.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem See below.

What is the expected behavior? Same log output whether the script is run from the console or from Start-RSJob.

Which versions of Powershell and which OS are affected by this issue? Did this work in previous versions of our scripts? PSv2 Win7

Please provide a code example showing the issue, if applicable: I've attached three files. Save them to C:\Temp (or edit test_RSjob.ps1):

test.ps1.txt - Sample script, excerpt from my larger program test_RSjob.ps1.txt - Calls test.ps1 via Start-RSJob. test_RSjob.log.txt - Log file showing before and after output from my machine (log file is appended).

test.ps1.txt test_RSJob.log.txt test_RSjob.ps1.txt

My console size is 200 x 80.

The line wrapping is kind of a big deal because the actual code has replaceable parameters ({0}, {1}, etc). I sometimes need to cut-and-paste the log lines into SQL Server Management Studio for debugging. The line wrapping results in invalid SQL unless I manually remove the extra CR's.

scottbass avatar Jun 15 '17 01:06 scottbass

Your script use Format-Table | Out-String, so it's may be Format-Table problem. Afaik, all of out-host-like cmdlets inherit the dimensions from the parent window.

Some time ago I wrote generic logging script, using start-transcript for saving all of script output including verbose. I want avoid line wrapping and set console window to it maximal size.

$console = $host.UI.RawUI
$bufSize = $console.BufferSize
$bufSize.Width = $host.UI.RawUI.MaxPhysicalWindowSize.Width
$console.BufferSize = $bufSize
$console.WindowSize = $console.MaxPhysicalWindowSize

for standart output it helps, but write-verbose still wrap it's output to width of original console!

Seems, new internal runspace buffers also get it's size from some defaults. because inside runspace $console.BufferSize -eq $console.WindowSize -eq $null

so, I suggest you remove any console interacting cmdlets from your script and use select-object. This way you can't get table output format, but list for logging even better :)

MVKozlov avatar Jun 15 '17 06:06 MVKozlov

Thanks. Yes I've used the above code in the past myself.

As a quick test (2-3 mins), can you do the following:

Open a PS console window. Change its width to something "large" (again mine is 200 x 80) Run test.ps1 Run test_RSJob.ps1 Review test_RSjob.log.

If the two outputs are different with respect to line wrapping, what would you expect them to be?

I haven't dug through the PoshRSJob code, but I wonder if the .Net class(es) is creating a "virtual console" (probably not the right term but hopefully gets the idea across) that has a line width of 80, rather than setting it to the size of the calling console window.

If that's the case, then what do you think it should do? IMO, it would be best if the "normal" invocation and the RSJob invocation created the same results.

Thanks...

scottbass avatar Jun 16 '17 03:06 scottbass

Tests are different. "Normal" run gets the same width with the console running, the rsjob test gives a 122-character result. Regardless of the current font. This value is different from yours. Even more, PsISE gives the same 122-width results.

I know how RSJob works, so I can say that it does not create some "internal console", it creates a runpace, runs a script in it and Receive-RSJob, receives content from all internal PS streams and writes them to corresponding streams in the current host. When the work is completed, your data is already in the output stream in formatted string form, due to * | Table Format | Out-string, so the formatting remains untouched by rsjob, powershell internally formats it.

Therefore, for the same results, try to avoid the cmdlets interacting with the host inside the "hostless" runspace. For example, try to output full object and format it after Receive-RSJob

MVKozlov avatar Jun 16 '17 06:06 MVKozlov

"it creates a runpace, runs a script in it and Receive-RSJob, receives content from all internal PS streams and writes them to corresponding streams in the current host."

Can you change the RSJobs code to setup the runspace and internal PS streams to have the same column width as the invoking console?

"For example, try to output full object and format it after Receive-RSJob"

This isn't going to happen. All formatting is done by the invoked script; I will not be moving that code to the RSJobs wrapper script.

scottbass avatar Jun 26 '17 04:06 scottbass

Once more:

  1. RSJob do nothing with "console" and $Host.UI.RawUI.MaxWindowSize -eq $null inside runspace.
  2. data streams doesnt have any width, data streams contains objects, not textual strings eveny it object have string type
  3. these objects formatted internally by powershell when converted to string objects.

You can ask why it doesn't use calling console sizes here https://github.com/PowerShell/PowerShell but I predict 'by design' answer. Mainly because runspace doesn't have any console related properties

PS C:\> $RunspacePool = [RunspaceFactory ]::CreateRunspacePool(1, 5)
PS C:\> $RunspacePool | gm
   TypeName: System.Management.Automation.Runspaces.RunspacePool

Name                          MemberType Definition
----                          ---------- ----------
StateChanged                  Event      System.EventHandler`1[System.Management.Automation.Runspaces.RunspacePoolStateChanged...
BeginClose                    Method     System.IAsyncResult BeginClose(System.AsyncCallback callback, System.Object state)
BeginConnect                  Method     System.IAsyncResult BeginConnect(System.AsyncCallback callback, System.Object state)
BeginDisconnect               Method     System.IAsyncResult BeginDisconnect(System.AsyncCallback callback, System.Object state)
BeginOpen                     Method     System.IAsyncResult BeginOpen(System.AsyncCallback callback, System.Object state)
Close                         Method     void Close()
Connect                       Method     void Connect()
CreateDisconnectedPowerShells Method     System.Collections.ObjectModel.Collection[powershell] CreateDisconnectedPowerShells()
Disconnect                    Method     void Disconnect()
Dispose                       Method     void Dispose(), void IDisposable.Dispose()
EndClose                      Method     void EndClose(System.IAsyncResult asyncResult)
EndConnect                    Method     void EndConnect(System.IAsyncResult asyncResult)
EndDisconnect                 Method     void EndDisconnect(System.IAsyncResult asyncResult)
EndOpen                       Method     void EndOpen(System.IAsyncResult asyncResult)
Equals                        Method     bool Equals(System.Object obj)
GetApplicationPrivateData     Method     psprimitivedictionary GetApplicationPrivateData()
GetAvailableRunspaces         Method     int GetAvailableRunspaces()
GetCapabilities               Method     System.Management.Automation.Runspaces.RunspacePoolCapability GetCapabilities()
GetHashCode                   Method     int GetHashCode()
GetMaxRunspaces               Method     int GetMaxRunspaces()
GetMinRunspaces               Method     int GetMinRunspaces()
GetType                       Method     type GetType()
Open                          Method     void Open()
SetMaxRunspaces               Method     bool SetMaxRunspaces(int maxRunspaces)
SetMinRunspaces               Method     bool SetMinRunspaces(int minRunspaces)
ToString                      Method     string ToString()
ApartmentState                Property   System.Threading.ApartmentState ApartmentState {get;set;}
CleanupInterval               Property   timespan CleanupInterval {get;set;}
ConnectionInfo                Property   System.Management.Automation.Runspaces.RunspaceConnectionInfo ConnectionInfo {get;}
InitialSessionState           Property   initialsessionstate InitialSessionState {get;}
InstanceId                    Property   guid InstanceId {get;}
IsDisposed                    Property   bool IsDisposed {get;}
RunspacePoolAvailability      Property   System.Management.Automation.Runspaces.RunspacePoolAvailability RunspacePoolAvailabil...
RunspacePoolStateInfo         Property   System.Management.Automation.RunspacePoolStateInfo RunspacePoolStateInfo {get;}
ThreadOptions                 Property   System.Management.Automation.Runspaces.PSThreadOptions ThreadOptions {get;set;}
PS C:\> $powershell = [powershell]::Create()
PS C:\> $powershell.RunspacePool = $RunspacePool
PS C:\> $powershell | gm
   TypeName: System.Management.Automation.PowerShell

Name                   MemberType Definition
----                   ---------- ----------
InvocationStateChanged Event      System.EventHandler`1[System.Management.Automation.PSInvocationStateChangedEventArgs] Invoca...
AddArgument            Method     powershell AddArgument(System.Object value)
AddCommand             Method     powershell AddCommand(string cmdlet), powershell AddCommand(string cmdlet, bool useLocalScop...
AddParameter           Method     powershell AddParameter(string parameterName, System.Object value), powershell AddParameter(...
AddParameters          Method     powershell AddParameters(System.Collections.IList parameters), powershell AddParameters(Syst...
AddScript              Method     powershell AddScript(string script), powershell AddScript(string script, bool useLocalScope)
AddStatement           Method     powershell AddStatement()
AsJobProxy             Method     System.Management.Automation.PSJobProxy AsJobProxy()
BeginInvoke            Method     System.IAsyncResult BeginInvoke(), System.IAsyncResult BeginInvoke[T](System.Management.Auto...
BeginStop              Method     System.IAsyncResult BeginStop(System.AsyncCallback callback, System.Object state)
Connect                Method     System.Collections.ObjectModel.Collection[psobject] Connect()
ConnectAsync           Method     System.IAsyncResult ConnectAsync(), System.IAsyncResult ConnectAsync(System.Management.Autom...
CreateNestedPowerShell Method     powershell CreateNestedPowerShell()
Dispose                Method     void Dispose(), void IDisposable.Dispose()
EndInvoke              Method     System.Management.Automation.PSDataCollection[psobject] EndInvoke(System.IAsyncResult asyncR...
EndStop                Method     void EndStop(System.IAsyncResult asyncResult)
Equals                 Method     bool Equals(System.Object obj)
GetHashCode            Method     int GetHashCode()
GetType                Method     type GetType()
Invoke                 Method     System.Collections.ObjectModel.Collection[psobject] Invoke(), System.Collections.ObjectModel...
Stop                   Method     void Stop()
ToString               Method     string ToString()
Commands               Property   System.Management.Automation.PSCommand Commands {get;set;}
HadErrors              Property   bool HadErrors {get;}
HistoryString          Property   string HistoryString {get;set;}
InstanceId             Property   guid InstanceId {get;}
InvocationStateInfo    Property   System.Management.Automation.PSInvocationStateInfo InvocationStateInfo {get;}
IsNested               Property   bool IsNested {get;}
IsRunspaceOwner        Property   bool IsRunspaceOwner {get;}
Runspace               Property   runspace Runspace {get;set;}
RunspacePool           Property   System.Management.Automation.Runspaces.RunspacePool RunspacePool {get;set;}
Streams                Property   System.Management.Automation.PSDataStreams Streams {get;}

even non-public

PS C:\> $Flags = 'nonpublic','instance','static'
PS C:\> $RunspacePool.gettype().GetProperties($flags) | Select-Object -ExpandProperty Name
IsRemote
RemoteRunspacePoolInternal
PS C:\> $RunspacePool.gettype().GetMethods($flags) | Select-Object -ExpandProperty Name
get_IsRemote
get_RemoteRunspacePoolInternal
add_InternalStateChanged
add_InternalForwardEvent
remove_InternalStateChanged
remove_InternalForwardEvent
add_InternalRunspaceCreated
remove_InternalRunspaceCreated
OnStateChanged
add_ForwardEvent
remove_ForwardEvent
OnInternalPoolForwardEvent
OnEventForwarded
add_RunspaceCreated
remove_RunspaceCreated
OnRunspaceCreated
BeginGetRunspace
CancelGetRunspace
EndGetRunspace
ReleaseRunspace
AssertPoolIsOpen
Finalize
MemberwiseClone
PS C:\> $Powershell.gettype().GetProperties($flags) | Select-Object -ExpandProperty Name
ErrorBuffer
ProgressBuffer
VerboseBuffer
DebugBuffer
WarningBuffer
InformationBuffer
InformationalBuffers
RedirectShellErrorOutputPipe
IsChild
EndInvokeAsyncResult
ErrorBufferOwner
OutputBufferOwner
OutputBuffer
IsGetCommandMetadataSpecialPipeline
RemotePowerShell
ExtraCommands
RunningExtraCommands
PS C:\> $Powershell.gettype().GetMethods($flags) | Select-Object -ExpandProperty Name
get_ErrorBuffer
set_ErrorBuffer
get_ProgressBuffer
set_ProgressBuffer
get_VerboseBuffer
set_VerboseBuffer
set_DebugBuffer
get_DebugBuffer
set_WarningBuffer
get_WarningBuffer
get_InformationBuffer
set_InformationBuffer
get_InformationalBuffers
get_RedirectShellErrorOutputPipe
set_RedirectShellErrorOutputPipe
get_IsChild
set_EndInvokeAsyncResult
get_EndInvokeAsyncResult
set_IsRunspaceOwner
set_ErrorBufferOwner
get_ErrorBufferOwner
set_OutputBufferOwner
get_OutputBufferOwner
get_OutputBuffer
get_IsGetCommandMetadataSpecialPipeline
set_IsGetCommandMetadataSpecialPipeline
get_RemotePowerShell
get_ExtraCommands
get_RunningExtraCommands
InitForRemotePipeline
InitForRemotePipelineConnect
Create
AddCommand
SetHadErrors
add_RunspaceAssigned
remove_RunspaceAssigned
SetRunspace
GetRunspaceConnection
CheckRunspacePoolAndConnect
InvokeWithDebugger
BeginBatchInvoke
BatchInvocationWorkItem
BatchInvocationCallback
DoRemainingBatchCommands
DetermineIsBatching
SetupAsyncBatchExecution
EndAsyncBatchExecution
AppendExceptionToErrorStream
PipelineStateChanged
GenerateNewInstanceId
GetSteppablePipeline
GetContextFromTLS
GetSteppablePipeline
IsCommandRunning
IsDisconnected
AssertExecutionNotStarted
AssertChangesAreAccepted
AssertNotDisposed
Dispose
InternalClearSuppressExceptions
RaiseStateChangeEvent
SetStateChanged
CloseInputBufferOnReconnection
ClearRemotePowerShell
SetIsNested
CoreInvoke
CoreInvokeHelper
CoreInvokeRemoteHelper
CoreInvoke
CoreInvokeAsync
VerifyThreadSettings
Prepare
CoreStop
ReleaseDebugger
StopHelper
StopThreadProc
ServerSupportsBatchInvocation
AddToRemoteRunspaceRunningList
RemoveFromRemoteRunspaceRunningList
GetRemoteRunspacePoolInternal
FromPSObjectForRemoting
ToPSObjectForRemoting
CommandsAsListOfPSObjects
SuspendIncomingData
ResumeIncomingData
WaitForServicingComplete
AsPSPowerShellPipeline
Finalize
MemberwiseClone

MVKozlov avatar Jun 26 '17 07:06 MVKozlov

there is a method of runspace creathion that uses host

static System.Management.Automation.Runspaces.RunspacePool CreateRunspacePool(int minRunspaces, int maxRunspaces, System.Management.Automation.Host.PSHost host)

but for this method you should use your own System.Management.Automation.Host.PSHost class.

It's quite difficult to use it and nobody will want to implement it for poorly designed script

MVKozlov avatar Jun 26 '17 07:06 MVKozlov