Archives for the Month of July, 2008

Workaround: The OS handle's position is not what FileStream expected

If you have a PowerShell script that you are calling from cmd.exe, you might run into the following error:

Write-Host : The OS handle's position is not what FileStream expected. Do not use a handle simultaneously in one FileStream and in Win32 code or another FileStream. This may cause data loss.

This is bug in PowerShell V1 and V2, and happens when:

  • a PowerShell command generates both regular and error output
  • you have used cmd.exe to redirect the output to a file
  • you have used cmd.exe to merge the output and error streams

Note: This bug was fixed in December 2012 along with the release of Windows Management Framework 3.0. This is the version of PowerShell that ships with Windows 8. Applying this update will resolve your issue. You can also install Windows Management Framework 4.0, which is the version released with Windows 8.1.

For example:

PowerShell -Command '"start"'; Write-Error "Foo"; '"end"' > c:\temp\redirect.log 2>&1

One workaround is to use Start-Transcript for file logging (rather than cmd.exe) or have PowerShell do the error stream redirection.

However, if you don't have control over your logging, you can add the following snippet to any scripts that get launched this way. Note that this code must be placed at the top of your script before any other code.

Note: this is an unsupported workaround. It will almost definitely break as future versions of PowerShell are released.

V1

$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$consoleHost = $host.GetType().GetField("externalHost", $bindingFlags).GetValue($host)
[void] $consoleHost.GetType().GetProperty("IsStandardOutputRedirected", $bindingFlags).GetValue($consoleHost, @())

$field = $consoleHost.GetType().GetField("standardOutputWriter", $bindingFlags)
$field.SetValue($consoleHost, [Console]::Out)
$field2 = $consoleHost.GetType().GetField("standardErrorWriter", $bindingFlags)
$field2.SetValue($consoleHost, [Console]::Out)

V2

$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$objectRef = $host.GetType().GetField("externalHostRef", $bindingFlags).GetValue($host)

$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty"
$consoleHost = $objectRef.GetType().GetProperty("Value", $bindingFlags).GetValue($objectRef, @())

[void] $consoleHost.GetType().GetProperty("IsStandardOutputRedirected", $bindingFlags).GetValue($consoleHost, @())
$bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
$field = $consoleHost.GetType().GetField("standardOutputWriter", $bindingFlags)
$field.SetValue($consoleHost, [Console]::Out)
$field2 = $consoleHost.GetType().GetField("standardErrorWriter", $bindingFlags)
$field2.SetValue($consoleHost, [Console]::Out)

How does this work, and why does this happen in the first place? When PowerShell sends output to its output stream the first time, it keeps a reference to the output stream for future use. However, this output stream is really a wrapper around a lower-level stream. When cmd.exe writes to the output stream, it writes to the lower-level stream. This makes the .NET wrapper complain that the underlying stream has changed from beneath it.

This workaround modifies some private engine state to not keep a reference to the output stream -- but instead to re-examine the output stream on every use.

[Edit 03/12/09 - Updated to also fix Standard Error, as mentioned in the PowerShellWizard Blog]

PowerShell Execution Policies in Standard Images

Once in awhile, we get questions about the best practice for PowerShell's execution policy when it is included as part of a standard desktop image.

This is one of the main reasons that PowerShell ships with a "Restricted" execution policy. Since you have to explicitly enable or install PowerShell, we've frequently been asked why we aren't more permissive by default. After all, you did just install / enable it! The reason is that separating the execution policy decision from the installation decision gives you a lot more freedom on installing PowerShell. It lets you push PowerShell to your whole enterprise via SMS (or include on a standard image,) and then later selectively configure the Execution Policy via Group Policy or other mechanisms.

When it comes to configuring these systems, the answer to this is largely determined by who will be using PowerShell. Is it primarily the end users, or as a target for logon scripts? The 80% rule is key here. You want to make a well-reasoned decision that applies to as many of your systems as possible, rather than have everybody make an un-informed decision on their own.

If it’s mostly for logon scripts, and you have a certificate you can sign these scripts with:

  • Set the execution policy to AllSigned
  • Add your signing certificate to the TrustedPublisher store

If scripting is likely to be extremely common (as with Exchange,) or you can’t obtain a code signing certificate, set the execution policy to RemoteSigned.

See here for some more guidance on this: http://www.leeholmes.com/blog/3rdPartiesAndPowerShellExecutionPolicies.aspx

As an brief caveat for logon scripts, some machines are configured to treat UNC paths as the same security zone as the internet (as opposed to the intranet.) This is Internet Explorer's "Enhanced Security Configuration." In this case, PowerShell responds the same as the Explorer Shell when it runs scripts from a UNC path: “While scripts from the internet can be useful, this script can potentially harm your computer. Do you want to run <script>?”

One way to fix this is by adding the source server to Internet Explorer’s Trusted Sites, or changing the “UncAsIntranet” configuration property (http://technet.microsoft.com/en-us/library/bb457150.aspx). This is also covered on page 341 of the (my) PowerShell Cookbook.

As another caveat, it is currently not possible to directly assign a .ps1 script as a logon script, since the architecture that enables this implies that you must be able to double-click on a PowerShell script to run it. We don’t want double-clicking to run a script by default for security reasons, so you can write a VBS script or batch file to call PowerShell with the correct arguments. The team blog goes into a bit more detail on this.