Our .NET application has the ability to generate .png files which are screenshots of a WPF Canvas.

This can be triggered from a client and handled by another of our applications (essentially something that has jobs farmed out to it that are queued and processed) that can be left running on another machine, which is then responsible for taking the screenshot.

This functionality works great and has done for a long time. This includes on VMs, even when there is nobody connected to that machine, i.e. there is no display rendered. We have not made any changes to the code.

However, since Windows 10 Version 1903 (May 2019 Update), the generated screenshots are always blank if they are generated on a virtual machine that nobody is connected to. It works fine if you are currently connected to the VM. We have also reproduced this issue on Updates 1909 and 2004.

The result that we get is a fully transparent .png of the correct size.

I have remotely debugged the virtual machine creating the screenshots and there is nothing obviously incorrect - all properties like heights, widths and visibility are correct. There are no exceptions thrown.

We think this must be related to the fact that there is no display that can be accessed or something similar. However, it is odd that this used to work fine, so we are stumped as to what could have changed between Windows updates despite our code remaining unchanged.

Has anyone else experienced this issue or similar and managed to work around it? I know that WPF isn't fully supported to be used in a service - does this use case cross over into that as the application technically has no display at the point of creating the render?

Here is a condensed example of our code for reference:

' A method that ensures all contents of the canvas has been loaded in, then sets the canvases height and width based on its children, 
' ensuring that it is layed out fully in preperation for the screenshot
  theCanvas.UpdateSize()

Try

    Dim renderBitmap As New RenderTargetBitmap(CInt(theCanvas.Width), CInt(theCanvas.Height), 96.0, 96.0, PixelFormats.Pbgra32)
    renderBitmap.Render(theCanvas)

    Dim directoryPath as String = Path.GetDirectoryName(saveLocation)
    Directory.CreateDirectory(directoryPath)

    Using outStream as New FileStream(saveLocation, FileMode.OpenOrCreate)

        Dim encoder As New PngBitmapEncoder()
        encoder.Frames.Add(BitmapFrame.Create(renderBitmap))

        encoder.Save(outStream)
    End Using

Catch ex as IOException
    ...
End Try
2

There are 2 best solutions below

0
On BEST ANSWER

We figured out what the problem was.

Microsoft fixed a Windows issue in 2017 and in doing so, shipped out default behavior where a setting called ShouldRenderEvenWhenNoDisplayDevicesAreAvailable would be set to False. Obviously this means that in our use case, WPF was not rendering anything as there was technically no display device available.

Adding the below to the app.config file of our application fixes the issue:

<runtime>
      <AppContextSwitchOverrides value="Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation=false;Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable=true" />
</runtime>
0
On

One of our applications had this same issue. As Daniel correctly points out, we could trace the root cause of this back to when we updated our environment to have this Microsoft update.

Our application used RenderTargetBitmap to generate images. It would render correctly whenever there was an active remote desktop session, since in this case it would detect valid display devices. However, whenever there wasn't an active remote desktop session, the rendering issue would occur because it couldn't detect any display devices. Therefore the result would be blank/transparent images.

Daniel's answer of updating the App.Config worked well for our scenario. But we also found that Microsoft has more documentation on this, most of which is detailed here under AppContext.

To summarize, there are three main approaches to opting out of the update. These are in order of precedence that Microsoft notes for which one will override the other:

(1) Change the setting programmatically, using the SetSwitch method like so:

AppContext.SetSwitch("System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable", true);

AppContext.SetSwitch("Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation", false);

(2) Change the App.Config file:

<configuration>
   <runtime>
      <AppContextSwitchOverrides value="Switch.System.Windows.Media.ShouldNotRenderInNonInteractiveWindowStation=false;Switch.System.Windows.Media.ShouldRenderEvenWhenNoDisplayDevicesAreAvailable=true"
   </runtime>
</configuration>

(3) Change the registry:

"Add a new string value to the HKLM\SOFTWARE\Microsoft.NETFramework\AppContext subkey. Set the name of the entry to the name of the switch. Set its value to one of the following options: True, true, False, or false.

On a 64-bit operating system, you must also add the same entry to the HKLM\SOFTWARE\Wow6432Node\Microsoft.NETFramework\AppContext subkey.

Using the registry to define an AppContext switch has machine scope; that is, it affects every application running on the machine."

Relevant links below:

https://support.microsoft.com/en-gb/topic/quality-rollup-for-net-framework-4-6-4-6-1-4-6-2-and-4-7-for-windows-server-2012-kb-4043762-6b1e1b85-5385-bf62-059a-2183b62d7153

https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.imaging.rendertargetbitmap.render

https://learn.microsoft.com/en-us/dotnet/api/system.appcontext

https://learn.microsoft.com/en-us/dotnet/api/system.appcontext.setswitch