WPF identify remote access (Not Winforms) - wpf

How can a WPF app know if its getting remotely operated (via VNC or remote desktop)?
In winforms there is System.Windows.Forms.SystemInformation.TerminalServerSession as per Detecting remote desktop connection but is there a strightforward way for this in WPF?
I guess the hack for now could be to have an invisible Winforms host on WPF and use its own capacity to host dummy win form that can identify the same... but that looks lame to me!
Any inputs would be appreciated!
Thx

I guess the hack for now could be to have an invisible Winforms host on WPF and use its own capacity to host dummy win form that can identify the same... but that looks lame to me!
You don't need an invisible WinForms host... you can just add a reference to the System.Windows.Forms assembly, and use the SystemInformation.TerminalServerSession static property.
If you don't want a dependency on WinForms, you can use the GetSystemMetrics Win32 API:
const int SM_REMOTESESSION = 0x1000;
[DllImport("user32")]
static extern int GetSystemMetrics(int nIndex);
public static bool IsTerminalServerSession()
{
return (GetSystemMetrics(SM_REMOTESESSION) & 1) != 0;
}

This method does not require a Windows Forms Window, only a reference to the DLL.
If you don't want to reference this, you can call the method to check this yourself, the implementation is as follows (I've wrapped it in a class):
static class SystemInformation
{
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern int GetSystemMetrics(int nIndex);
public static bool IsTerminalServerSession
{
get
{
//copied the Windows Forms implementation
return (GetSystemMetrics(0x1000) & 1) != 0;
}
}
}

Related

AssemblyLoadContext.Unload does not unload a Wpf Library

I am writing here the same issue posted on Github since I don't see much traffic there recently.
.NET Core Version: 3.1.9 and .Net 5
Windows version: 10.0.18363
Does the bug reproduce also in WPF for .NET Framework 4.8?: AssemblyLoadContext not supported
I am trying to load and unload on demand a Wpf App library (and all the related dependencies). Everything works, but none of the assemblies get unloaded when calling the Unload method.
If I replace the Wpf library with a .Net Core library containing a few sample methods, I can see the library removed from VS Modules window after a couple of GC iterations.
If I'm not wrong I should expect AssemblyLoadContext to load WpfLibrary and related dependencies (PresentationCore, PresentationFramework etc), but WpfLibrary is the only one loaded. All the other dependencies seems be loaded in the default context. May be that I misunderstood how it works, but to me seems that the framework dependencies prevent the unloading.
Also I am not sure if the problem I am reporting is related to this and/or this.
I attached a sample project which is structured like this:
Project 1 (MainApp, a console project with added System.Windows.Forms reference to enable message pump)
class Program
{
class WpfAppAssemblyLoadContext : AssemblyLoadContext
{
public WpfAppAssemblyLoadContext() : base(true) { }
protected override Assembly Load(AssemblyName assemblyName) => null;
}
[MethodImpl(MethodImplOptions.NoInlining),]
public static void TestRun()
{
var context = new WpfAppAssemblyLoadContext();
var assembly = context.LoadFromAssemblyPath($"{Environment.CurrentDirectory}\\WpfLibrary.dll");
var inst = (IProxy) assembly.CreateInstance("WpfLibrary.MainWindow");
inst.ShowWindow();
inst.CloseWindow();
context.Unload();
assembly = null;
context = null;
inst = null;
}
[STAThread,]
static void Main(string[] args)
{
TestRun();
for (var i = 0; i < 100; i++) {
GC.Collect();
GC.WaitForPendingFinalizers();
}
Application.Run();
}
}
Project 2 (ProxyInterface)
namespace ProxyInterface
{
public interface IProxy
{
void ShowWindow();
void CloseWindow();
}
}
Project 3 (a regular wpf library with implementation of interface in Project 2 )
namespace WpfLibrary
{
public partial class MainWindow : Window, IProxy
{
public MainWindow()
{
InitializeComponent();
}
public void ShowWindow() { Show();}
public void CloseWindow() { Close();}
}
}
UnloadWpfLibrary.zip
(Solution file inside "MainApp" folder)
Further updates:
DotNet team added the issue to "Future Milestone", therefore I have to deduce that they recognized this as a bug. I have no idea on when we will see Wpf working with AssemblyLoadContext.
Seems to be that there is a workaround which involve splitting the target assembly into two separate assemblies. I attached the project with the suggested modifications and this time one of the two assemblies is unloaded, but all of the others are still loaded included WpfLibrary.
UnloadWpfLibraryWithWorkaround.zip
I think that for me it's time to give up and recur to IPC (named pipes) although I am not sure if this could be a valid replacement.
May be I missed something and someone more expert can do further progress and attach here the project with the correct modifications, it would be of great benefit for all the users that want to use ALC to load and unload WPF.
It would be a total of 4 projects just to load and unload a wpf assembly on demand and this is not exactly clean, but if the final result is the same it would be acceptable.

Using RunningObjectTable alongside WPF WebBrowser

We are hosting a 3rd party web page in a WPF WebBrowser control. The 3rd party web site uses a VBScript GetObject(, "Prog.Id") call to get access to an already instantiated object.
I am aware this is a silly idea so please don't just answer telling me that. We need to integrate with this site and they are not in a position to change their VBScript approach.
Using PInvoke I can call RegisterActiveObject and GetActiveObject from within my WPF app code.
[DllImport("oleaut32.dll")]
public static extern int RegisterActiveObject([MarshalAs(UnmanagedType.IUnknown)] object punk, ref Guid rclsid, uint dwFlags, out int pdwRegister);
[DllImport("oleaut32.dll", CharSet = CharSet.None, ExactSpelling = false)]
public static extern void GetActiveObject(ref Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out Object ppunk);
If I use GetObject in the WPF code after RegisterActiveObject I get the previously instantiated version but the VBScript running in the WebBrowser control cannot see the running instance
This is the code to Register an instance with the ROT
int regResult = RegisterActiveObject(gatewayObject, ref gatewayClsIdGuid, 0, out this.objectRegisterValue);
The VBScript GetObject call that is not seeing the registered instance is
If (existing = True) Then
MsgBox("Getting not creating")
Set objTest = GetObject(, progId)
Else
Set objTest = CreateObject(progId)
End if
On Error Goto 0
If (objTest Is Nothing) Then
MsgBox("Unable to get object")
Else
MsgBox("Got the object successfully")
End If
Why is the GetObject(, progId) call returning Nothing?

How do oob silverlight application communicate through windows

If I have two windows in an oob application how do I communicate between them?
This is the new feature of silverlight 5 that allows for multiple windows.
They run in a common application. Hence they share the same static data. The scope of communication choices are therefore very large. Here is an example:-
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(object payload)
{
Payload = payload;
}
public object Payload {get; private set; }
}
public class Messenger
{
private static readonly Messenger _current = new Messenger();
public static Messenger Current { get { return _current; } }
public event EventHandler<MessageEventArgs> MessageReceived;
public void Send(object payload)
{
if (MessageReceived != null)
MessageReceived(this, new MessageEventArgs(payload));
}
}
All windows can attach a handler to Messenger.Current.MessageReceived (just be sure to detach when the window closes) and any window can call Messenger.Current.Send.
Ok so you wouldn't actually use this code its a bit rubbish, the point is Windows in SL5 are not isolated. You can create whatever internal application communication mechanism you need.
Option 1: MVVM Pattern
Both windows share a reference to the same view-model. Changes made by one are seen by both.
Option 2: Normal references
Window A can how a refernce to Windows B when it creates it.
Option 3: Message Passing
You can have a global event that you subscribe to in the Load event. (Make sure you unsubscribe in the Unload event or you will leak memory!) Windows can post messages to that event which the other windows listen for.

Persistent cookies in WPF WebBrowser control?

I'm using the WPF WebBrowser to display online help inside an app (just a few small web pages). Some of those pages use cookies to display items only for the first few times the pages are viewed (it's a "Why not try X" type of thing).
However, for some reason the cookies don't seem to be working inside the WebBrowser control. They work fine in full IE as well as Firefox and Chrome (so the items correctly hide), but they never hide when viewed through the WPF WebBrowser control.
Is there something special about using cookies in the WPF WebBrowser control? It seems to be behaving as if all the cookies are only stored in memory, rather than being persisted on disk.
Here's one of those pages inside a browser (where the cookies work):
And here's the exact same page inside the app:
That additional content should only be visible for the first few times of using the software (i.e. it should be hidden after N views of that web page), but because I can't get cookies to work it's always visible.
Cookies handling in Internet Explorer (or hosted versions) is tied to the IE's own notion of "URL Security Zones", doc here: About URL security Zones
So, IE determines an url zone using various alogorithms applied to the url. Depending on the zone, your hosted browser may or may not support session or persistent cookies.
Strangely, when I create a small WPF sample, add the web browser to it and have navigate to this persistent cookie tester utiliy page: http://www.rbaworld.com/Security/Computers/Cookies/givecook.shtml, it works fine. Each time I launch the sample app, the counter is incremented fine, so not everyone can reproduce your problem. Well, that's the whole purpose of URL Security zones: it can vary by machine, by user, by Windows policy, etc...
The next question is: Can I change the zone you're running in? The short and easy answer is ... no because it's heavily tied to the security.
If you were hosting IE yourself, you could implement your own security zone handle as described here: Implementing a Custom Security Manager and a sample here: SAMPLE: Secumgr.exe Overrides Security Manager for WebBrowser Host but you're relying on WPF's webbrowser that does not allow any override... You can get to Reflector and copy all WPF private/internal code but that's a log of risky work!
The last thing you can try is to manipulate the standard Internet Security Manager. Here is some sample code that gives some hints. At least you should be able to determine the zone you're running in (MapUrltoZone) and change the cookie (TryAllowCookie). The problem with the standard manager is most of the times, it pops up dialog to the end-user allowing authorization... (security again!):
[ComImport, Guid("7b8a2d94-0ac9-11d1-896c-00c04Fb6bfc4")]
private class InternetSecurityManager
{
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("79eac9ee-baf9-11ce-8c82-00aa004ba90b")]
private interface IInternetSecurityManager
{
void Unused1();
void Unused2();
[PreserveSig]
int MapUrlToZone([In, MarshalAs(UnmanagedType.BStr)] string pwszUrl, out int pdwZone, [In] int dwFlags);
void Unused3();
[PreserveSig]
int ProcessUrlAction(string pwszUrl, int dwAction, ref int pPolicy, int cbPolicy, ref Guid pContext, int cbContext, int dwFlags, int dwReserved);
// left undefined
}
public static SecurityZone MapUrlToZone(Uri uri)
{
IInternetSecurityManager securityManager = (IInternetSecurityManager)new InternetSecurityManager();
int zoneId;
if (securityManager.MapUrlToZone(uri.ToString(), out zoneId, 0) < 0)
return SecurityZone.NoZone;
return (SecurityZone)zoneId;
}
private const int URLACTION_COOKIES = 0x00001A02;
private const int URLACTION_COOKIES_ENABLED = 0x00001A10;
private const int URLPOLICY_ALLOW = 0x00;
private const int URLPOLICY_DISALLOW = 0x03;
private const int PUAF_DEFAULT = 0x00000000;
public static bool TryAllowCookies(Uri uri)
{
IInternetSecurityManager securityManager = (IInternetSecurityManager)new InternetSecurityManager();
int policy = 0;
Guid context = Guid.Empty;
int hr = securityManager.ProcessUrlAction(uri.ToString(), URLACTION_COOKIES_ENABLED, ref policy, Marshal.SizeOf(policy), ref context, Marshal.SizeOf(context), PUAF_DEFAULT, 0);
return (hr == 0) && policy == URLPOLICY_ALLOW;
}
Good luck :)
The WebBrowser control won't allow this by default. For security reasons, you probably wouldn't want different applications from different developers/companies being able to access cookie info that another app created.
However, check out this answer How to delete Cookies from windows.form?
That pertains to deleting cookies through javascript, but you may be able to use a similar method in order to persist and create the site cookie each time the application is loaded.

Managing cookies in a WPF WebBrowser control?

Is there a way to read/write the cookies that a WebBrowser control uses?
I am doing something like this...
string resultHtml;
HttpWebRequest request = CreateMyHttpWebRequest(); // fills http headers and stuff
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
using (StreamReader sr = new StreamReader(response.GetResponseStream()))
{
resultHtml = sr.ReadToEnd();
}
WebBrowser browser = new WebBrowser();
browser.CookieContainer = request.CookieContainer; // i wish i could do this :(
browser.NavigateToString(resultHtml);
One of the potentially confusing things about the WebBrowser control and cookies is that at a first glance, it often looks like your app gets a separate cookie store. For example, if you log into a site that stores a persistent cookie to identify you, then whether you appear to be logged in for that site from inside an app hosting the control will be independent of whether you seem to be logged in via Internet Explorer.
In fact, you can even be logged in with different identities.
However, although it might be natural to draw the conclusion that each app hosting the WebBrowser therefore gets its own cookies, in fact that's not true. There are merely two sets of cookies: the ones used in 'low integrity' mode (which is what IE runs in by default), and the other set, which is what you'll get in a normal app that hosts the WebBrowser and also what you'll get if you run IE elevated.
the webbrowser control uses WinInet for networking, specifically use the InternetSetCookie(Ex) and InternetGetCookie(Ex) functions for Cookie management. There isn't a WinInet wrapper in .Net, but you can p-invoke.
Yes you are right, InternetGetCookieEx is the only way to retrieve HttpOnly cookies and it is the preferred way to grab cookie from WebBrowser control.
I posted a complete example here
You can use Application.GetCookie and Application.SetCookie methods.
Although Application is more or less related to WPF, you can use these methods in any desktop .NET code. In fact, they are wrappers on InternetGetCookieEx and InternetSetCookieEx Windows APIs.
I faced the same issue few days ago.
Besides the examples of the previous answers, here is a Win32 wrapper for the WebBrowser control. The advantage of this implementation is that it exposes more options that the default WebBrowser control.
Unfortunately if It's not WPF native, so you will have to create a wrapper if you're planning to use it in WPF.
http://code.google.com/p/csexwb2/
Here is sample from [link][1]
> public static class WinInetHelper
{
public static bool SupressCookiePersist()
{
// 3 = INTERNET_SUPPRESS_COOKIE_PERSIST
// 81 = INTERNET_OPTION_SUPPRESS_BEHAVIOR
return SetOption(81, 3);
}
public static bool EndBrowserSession()
{
// 42 = INTERNET_OPTION_END_BROWSER_SESSION
return SetOption(42, null);
}
static bool SetOption(int settingCode, int? option)
{
IntPtr optionPtr = IntPtr.Zero;
int size = 0;
if (option.HasValue)
{
size = sizeof(int);
optionPtr = Marshal.AllocCoTaskMem(size);
Marshal.WriteInt32(optionPtr, option.Value);
}
bool success = InternetSetOption(0, settingCode, optionPtr, size);
if (optionPtr != IntPtr.Zero) Marshal.Release(optionPtr);
return success;
}
[System.Runtime.InteropServices.DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool InternetSetOption(
int hInternet,
int dwOption,
IntPtr lpBuffer,
int dwBufferLength
);
}

Resources