How can I get my Windows service to see objects in ROT? - c#

EDIT: Though tangentially related, the tagged "duplicate" is not a true duplicate of this question, and more importantly does not answer this question (because the problem is different). I am working with a Window Service, not a standalone userspace app.
Here is what I'm working with:
I've written a Windows service (running in Windows Server 2019) in C# that interacts with other software via COM. This other software is not software that I have access to the source for, so I cannot make changes to the COM registration flags or anything like that, but it does register itself and has an API.
I'd like to use the running object table (ROT) from my service to grab an instance of the other software via moniker display name, however the ROT is always empty from the perspective of my service, even after the service itself starts an instance of the other software via Process.Start() which returns a valid PID. To be clear, this runs fine from a standalone Windows console or Windows form application, but not as a service.
The service is configured to log in as a user who is in the administrators group on this machine. Why can't it see anything in the ROT, even after starting a process itself?
Here is a sample code snippet. When run in a console app it counts many monikers, when run in my service, counter is always 0, even after it starts a process.
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
private static void TestRot()
{
IBindCtx context = null;
IRunningObjectTable rot = null;
IEnumMoniker monikers = null;
log.Debug("About to start moniker check");
CreateBindCtx(0, out context);
context.GetRunningObjectTable(out rot);
rot.EnumRunning(out monikers);
var moniker = new IMoniker[1];
log.Debug("Beginning moniker loop");
var counter = 0;
while (monikers.Next(1, moniker, IntPtr.Zero) == 0)
{
counter++;
var curMoniker = moniker.First();
string name = null;
if (curMoniker != null)
{
try
{
curMoniker.GetDisplayName(context, null, out name);
}
catch (UnauthorizedAccessException)
{
log.Debug($"UnauthorizedAccessException when getting display name for curMoniker");
}
}
log.Debug($"curMoniker: {name}");
}
log.Debug("Counted monikers: " + counter);
}
Thanks in advance.

Related

How to retrieve IVsDebugger from external DTE for automation in Visual Studio 2019

I am trying to write a VSIX for Visual Studio 2019 that controls multiple instances of the Visual Studio IDE. We are working on a networked project that requires some automation to perform testing of multiple users. In the past I would have used DTE in an external tool, but my understanding is that as of VS2017 the COM guids are no longer globally registered, so doing it within the IDE is the only way.
Regardless, I am trying to get the IVsDebugger so I can track events in the debugger. However, I am having no luck. I can get IVsDebugger2, 3, 4, 5 but not IVSDebugger. Here is the general flow of what I am doing:
void CaptureDebugger()
{
DTE dte = GetDTE(GetRemoteProcessID());
ServiceProvider sp = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
IVsDebugger vsDebugger = sp.GetService(typeof(SVsShellDebugger)) as IVsDebugger;
// vsDebugger is null!
IVsDebugger2 vsDebugger2 = sp.GetService(typeof(SVsShellDebugger)) as IVsDebugger2;
// vsDebugger2 is not null!
}
/// <summary>
/// Gets the DTE object from any devenv process.
/// </summary>
private static EnvDTE.DTE GetDTE(int processId)
{
object runningObject = null;
IBindCtx bindCtx = null;
IRunningObjectTable rot = null;
IEnumMoniker enumMonikers = null;
try
{
Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
bindCtx.GetRunningObjectTable(out rot);
rot.EnumRunning(out enumMonikers);
IMoniker[] moniker = new IMoniker[1];
IntPtr numberFetched = IntPtr.Zero;
while (enumMonikers.Next(1, moniker, numberFetched) == 0)
{
IMoniker runningObjectMoniker = moniker[0];
string name = null;
try
{
if (runningObjectMoniker != null)
{
runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
}
}
catch (UnauthorizedAccessException)
{
// Do nothing, there is something in the ROT that we do not have access to.
}
Regex monikerRegex = new Regex(#"!VisualStudio.DTE\.\d+\.\d+\:" + processId, RegexOptions.IgnoreCase);
if (!string.IsNullOrEmpty(name) && monikerRegex.IsMatch(name))
{
Marshal.ThrowExceptionForHR(rot.GetObject(runningObjectMoniker, out runningObject));
}
}
}
finally
{
if (enumMonikers != null)
Marshal.ReleaseComObject(enumMonikers);
if (rot != null)
Marshal.ReleaseComObject(rot);
if (bindCtx != null)
Marshal.ReleaseComObject(bindCtx);
}
return runningObject as EnvDTE.DTE;
}
What confuses me is I get get the local IVsDebugger via the call
var MYDEBUGGER = Package.GetGlobalService(typeof(SVsShellDebugger)) as IVsDebugger;
Which I see is using a GlobalService. I don't think there is an equivalent in the DTE I retrieve.
Any insight?
I ran into this issue as well (however in my case, I'm actually trying to retrieve the IVsDebugger in proc rather than what sounds like out of proc); after debugging into how vsdebug!CDebugger::QueryInterface works I determined the actual issue appears to be that the calling thread in your application needs to be STA.
When the calling thread in your application is MTA, while vsdebug!CDebugger::QueryInterface returns with HRESULT 0
This shortly gets turned into 0x80040155 (REGDB_E_IIDNOTREG) by OLE due to CStdWrapper::GetPSFactory failing to find a proxy DLL for this type
This error in turn gets converted by CRemoteUnknown::RemQueryInterface to 0x80004002 (E_NOINTERFACE)
Which is what is reported back to you if you try and Marshal.QueryInterface in C# to see what's going on directly.
If your program contains in-proc components that live inside the remote Visual Studio process (as mine does) you can retrieve and execute your operations against the IVsDebugger on the UI thread. Otherwise, you can potentially create a new Thread and call thread.SetApartmentState(ApartmentState.STA) on it prior to starting it

Tell if any logged on user is running the application about to be installed/uninstalled

My program uses Inno Setup to install/uninstall it. In my application code I create a Global mutex using the CreateMutex Windows API function. Then in my Inno Setup program I have the following code:
AppMutex=Global\MyProgramMutex.2A23834B-2919-4007-8C0A-3C7EDCA7186E
function InitializeSetup(): Boolean;
begin
Result := True;
if (CreateMutex(0, False, '{#SetupSetting('AppId')}') <> 0) and (DLLGetLastError = ERROR_ALREADY_EXISTS) then
begin
Result := False;
MsgBox('Another instance of the Setup program is already running. Please close it and try again', mbCriticalError, MB_OK);
end;
if CheckForMutexes('{#SetupSetting('AppMutex')}') then
begin
Result := False;
MsgBox('{#SetupSetting('AppName')} ' + 'appears to be running. Please close all instances of the program before continuing.', mbCriticalError, MB_OK);
end;
end;
This works great, as expected, for the user running the Inno Setup program. The question/problem I have is: If I "Switch User" and start the application as a different user, and then switch back to the original user, the Setup program does not detect that the application is running under a different user.
I'm not knowledgeable all round enough to know, if the Setup program can detect the running application.
As documented, in Inno Setup KB Detect instances running in any user session with AppMutex:
To detect mutexes created in other sessions, your application must create two mutexes: one with a Global\ prefix and the other without.
Mutexes with the Global\ prefix are accessible from any user session. A like-named mutex must also be created in the session namespace (i.e. without the Global\ prefix) in case the creation of the Global mutex failed due to security restrictions or lack of operating system support (versions of Windows NT prior to 4.0 Terminal Server Edition don't support the Global\ prefix).
Additionally, a special security descriptor must be passed in each of the CreateMutex() calls to ensure the mutex is accessible by different users.
To make a mutex accessible by all users in C#, see:
What is a good pattern for using a Global Mutex in C#?
In sum, the code in your C# application should be like:
const string mutexId = "MyProg";
MutexAccessRule allowEveryoneRule =
new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.FullControl, AccessControlType.Allow);
MutexSecurity securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryoneRule);
Mutex globalMutex = null;
try
{
bool createdNew;
globalMutex =
new Mutex(false, "Global\\" + mutexId, out createdNew, securitySettings);
}
catch (UnauthorizedAccessException)
{
// Ignore
}
Mutex localMutex = new Mutex(false, mutexId);
try
{
// Run your program here
}
finally
{
// These have to be called only after the application (its windows) closes.
// You can also remove these calls and let the system release the mutexes.
if (globalMutex != null)
{
globalMutex.Dispose();
}
localMutex.Dispose();
}
On Inno Setup side, all you need is to list both mutexes in the AppMutex directive:
[Setup]
AppMutex=MyProg,Global\MyProg
You do not need your CreateMutex and CheckForMutexes calls in the InitializeSetup funcion.
Great idea Martin. Here is my full solution for using mutex objects in WPF that an Inno Setup program will detect, even when other logged-in users are running the WPF app. BTW. I used Visual Studio.
Assume that both the WPF app and project are called 'MyWPFApp'
Open the project properties for MyWPFApp; on the 'Application' tab ensure that the startup object is 'MyWPFApp.App'.
Change the Build Action of App.xaml from ApplicationDefinition to Page.
Remove the StartupUri property from App.xaml, if used.
If an Application.Startup event is used, remove any code that instantiates and displays the MainWindow.
Add the following, or similar, code to App.xaml.cs as part of the App class.
public partial class App : Application
{
private static readonly string _MutexID = "MyWPFApp"; // or whatever
[STAThread]
public static void Main()
{
var application = new App();
application.InitializeComponent();
MutexAccessRule allowEveryoneRule = new MutexAccessRule(
new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.FullControl,
AccessControlType.Allow);
MutexSecurity securitySettings = new MutexSecurity();
securitySettings.AddAccessRule(allowEveryone);
Mutex globalMutex = null;
try
{
bool createdNew;
globalMutex = new Mutex(false, "Global\\" + _MutexID, out createdNew, securitySettings);
}
catch (UnauthorizedAccessException)
{
// ignore
}
Mutex localMutex = new Mutex(false, _MutexID);
try
{
MainWindow mainWin = new MainWindow();
application.Run(mainWin);
}
finally
{
if (globalMutex != null)
{
globalMutex.Dispose();
}
localMutex.Dispose();
}
}
}
The final step is to include the following line in the Inno Setup script:
[Setup]
AppMutex=MyWPFApp,Global\MyWPFApp
I tried to architect the code using C# using statements for both mutexes, but I got brain freeze.
Alternatively, one can also create a separate class with a Main method in it, and place the above code there. This requires steps 4 and 5 above, and in step 2 change the Startup object to the new class containing the Main method.
Thanks Martin.
Bob

C# Nested impersonation - impersonate a user whilst already impersonating another

I have a fairly odd requirement to be able to impersonate a user, when I'm already impersonating another, using C#.
I'm writing an app to allow the management of Active Directory users. This app will provide the ability for anyone in the company to view and maintain certain details about themselves (some of which will not actually be saved to Active Directory, but some of which will), for managers to be able to view and maintain details about their team, and for HR to be able to view and maintain details about anyone.
For obvious reasons I don't want to develop or test this against the live domain. We have recently ported all users over to this domain from another domain, which means I can actually test against the old domain without affecting anything. However, to enable me to do this I have to impersonate my old account on the old domain, which I do on loading the application.
Although for me everything will work fine as I'm setup as a domain admin, going forward obviously not all users will be domain admins, and won't be able to write to AD under their own account, and therefore we have another domain admin user setup specifically for this application, whenever data needs to be saved to AD that user is impersonated. This was working great before when I was testing against an Active Directory I'd setup on a virtual machine because I was logging onto the local domain, however that didn't allow me to step through the code in Visual Studio so debugging was slow, and hence I've stopped using that virtual machine and am using this old domain. Now I'm already impersonating another user (i.e. my old domain account), when it then tries to impersonate the domain admin user it fails with an "System.Security.SecurityException: Access is denied." exception. The line this fails on is just writing out some debugging information using "WindowsIdentity.GetCurrent().Name".
If I change my code so I'm actually logging in using the new domain admin rather than my old account, the first time it goes through it logs in successfully (so the credentials are correct), however when it then goes through and tries to do the same again to write to AD it fails with the above exception. Therefore I think it must be a problem with trying to do a nested impersonate.
Is it possible to do a nested impersonate?
Below is the code I'm using:
private static WindowsImpersonationContext ImpersonateUser(out string result, string sUsername,
string sDomain, string sPassword)
{
// initialize tokens
var pExistingTokenHandle = new IntPtr(0);
var pDuplicateTokenHandle = new IntPtr(0);
// if domain name was blank, assume local machine
if (sDomain == "")
{
sDomain = Environment.MachineName;
}
try
{
result = null;
const int logon32ProviderDefault = 0;
// create token
const int logon32LogonInteractive = 2;
// get handle to token
var bImpersonated = LogonUser(sUsername, sDomain, sPassword,
logon32LogonInteractive,
logon32ProviderDefault,
ref pExistingTokenHandle);
// did impersonation fail?
if (!bImpersonated)
{
var nErrorCode = Marshal.GetLastWin32Error();
result = "LogonUser() failed with error code: " + nErrorCode + "\r\n";
}
// Get identity before impersonation
result += string.Format("Before impersonation: {0}\r\n", WindowsIdentity.GetCurrent().Name);
var bRetVal = DuplicateToken(pExistingTokenHandle, (int)SecurityImpersonationLevel.SecurityImpersonation,
ref pDuplicateTokenHandle);
// did DuplicateToken fail?
if (bRetVal)
{
// create new identity using new primary token
var newId = new WindowsIdentity(pDuplicateTokenHandle);
var impersonatedUser = newId.Impersonate();
// check the identity after impersonation
result += "After impersonation: " + WindowsIdentity.GetCurrent().Name + "\r\n";
return impersonatedUser;
}
else
{
var nErrorCode = Marshal.GetLastWin32Error();
CloseHandle(pExistingTokenHandle); // close existing handle
result += "DuplicateToken() failed with error code: " + nErrorCode + "\r\n";
return null;
}
}
finally
{
// close handle(s)
if (pExistingTokenHandle != IntPtr.Zero)
{
CloseHandle(pExistingTokenHandle);
}
if (pDuplicateTokenHandle != IntPtr.Zero)
{
CloseHandle(pDuplicateTokenHandle);
}
}
}
When this is called for the nested impersonation which fails, "bImpersonated" is actually "true", as is bRetVal, which suggests its worked, however when it gets to "WindowsIdentity.GetCurrent().Name" it fails with the exception above.
I hope this makes sense, and would appreciate any assistance.

SetupDiChangeState throws Access Denied

My user is an Administrator (I see it in the configuration panel), the below code throws a Win32Exception in which it says Access Denied, how can I change this (Win7 32 bits) ?
static Guid VideoGuid = new Guid("4d36e968-e325-11ce-bfc1-08002be10318");
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
static void Main(string[] args)
{
SafeDeviceHandle handle = null;
try
{
handle = NativeMethods.SetupDiGetClassDevs(ref VideoGuid, IntPtr.Zero, IntPtr.Zero, NativeMethods.DIGCF.PRESENT);
var data = new NativeMethods.SP_DEVINFO_DATA().Initialize();
var param = new NativeMethods.SP_PROPCHANGE_PARAMS().Initialize();
param.ClassInstallHeader.InstallFunction = 0x12;
param.StateChange = NativeMethods.DICS.ENABLE; // 0x01
param.Scope = NativeMethods.DICS_GLOBAL.GLOBAL; // 0x01
param.HwProfile = 0;
RunWin32Method(() => NativeMethods.SetupDiEnumDeviceInfo(handle, 0u, out data));
RunWin32Method(() => NativeMethods.SetupDiSetClassInstallParams(handle, ref data, ref param, (UInt32)Marshal.SizeOf(param)));
RunWin32Method(() => NativeMethods.SetupDiChangeState(handle, ref data));
}
catch
{
var w = new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
if (handle != null && (!handle.IsInvalid))
handle.Close();
}
}
static void RunWin32Method(Func<bool> f)
{
if (!f())
{
Debug.WriteLine(new Win32Exception(Marshal.GetLastWin32Error()).Message);
}
}
If you want more code, just ask :-)
Thanks
Recapping the comment trail, a user in the Administrator group doesn't have admin rights on Vista/Server 2008 and later unless the process runs elevated. A manifest is required to get Windows to display the UAC elevation prompt.
This cannot work for programs that are started at login by the Run registry key or the Startup folder. Windows refuses to display the elevation prompt because the user cannot accurately guess exactly what program asked for the elevation. Code-signing the program with a certificate may fix this since that permits Windows to verify and display the program owner, never actually tried that.
Workarounds for such programs are activating it as a service or a scheduled task. Neither of which requires the manifest. The theory behind this seeming oddity is that it already requires elevation to get a service or scheduled task installed.

How can a C# Windows Console application tell if it is run interactively

How can a Windows console application written in C# determine whether it is invoked in a non-interactive environment (e.g. from a service or as a scheduled task) or from an environment capable of user-interaction (e.g. Command Prompt or PowerShell)?
[EDIT: 4/2021 - new answer...]
Due to a recent change in the Visual Studio debugger, my original answer stopped working correctly when debugging. To remedy this, I'm providing an entirely different approach. The text of the original answer is included at the bottom.
1. Just the code, please...
To determine if a .NET application is running in GUI mode:
[DllImport("kernel32.dll")] static extern IntPtr GetModuleHandleW(IntPtr _);
public static bool IsGui
{
get
{
var p = GetModuleHandleW(default);
return Marshal.ReadInt16(p, Marshal.ReadInt32(p, 0x3C) + 0x5C) == 2;
}
}
This checks the Subsystem value in the PE header. For a console application, the value will be 3 instead of 2.
2. Discussion
As noted in a related question, the most reliable indicator of GUI vs. console is the "Subsystem" field in the PE header of the executable image. The following C# enum lists the allowable (documented) values:
public enum Subsystem : ushort
{
Unknown /**/ = 0x0000,
Native /**/ = 0x0001,
WindowsGui /**/ = 0x0002,
WindowsCui /**/ = 0x0003,
OS2Cui /**/ = 0x0005,
PosixCui /**/ = 0x0007,
NativeWindows /**/ = 0x0008,
WindowsCEGui /**/ = 0x0009,
EfiApplication /**/ = 0x000A,
EfiBootServiceDriver /**/ = 0x000B,
EfiRuntimeDriver /**/ = 0x000C,
EfiRom /**/ = 0x000D,
Xbox /**/ = 0x000E,
WindowsBootApplication /**/ = 0x0010,
};
As easy as that code (in that other answer) is, our case here can be vastly simplified. Since we are only specifically interested in our own running process (which is necessarily loaded), you don't have to open any file or read from the disk to obtain the subsystem value. Our executable image is guaranteed to be already mapped into memory. And it is simple to retrieve the base address for any loaded file image by calling the GetModuleHandleW function:
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandleW(IntPtr lpModuleName);
Although we might provide a filename to this function, again things are easier and we don't have to. Passing null, or in this case, default(IntPtr.Zero) (which is the same as IntPtr.Zero), returns the base address of the virtual memory image for the current process. This eliminates the extra steps (alluded to earlier) of having to fetch the entry assembly and its Location property, etc. Without further ado, here is the new and simplified code:
static Subsystem GetSubsystem()
{
var p = GetModuleHandleW(default); // VM base address of mapped PE image
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
return (Subsystem)Marshal.ReadInt16(p + 0x5C); // PE offset to 'Subsystem' word
}
public static bool IsGui => GetSubsystem() == Subsystem.WindowsGui;
public static bool IsConsole => GetSubsystem() == Subsystem.WindowsCui;
[official end of the new answer]
3. Bonus Discussion
For the purposes of .NET, Subsystem is perhaps the most—or only—useful piece of information in the PE Header. But depending on your tolerance for minutiae, there could be other invaluable tidbits, and it's easy to use the technique just described to retrieve additional interesting data.
Obviously, by changing the final field offset (0x5C) used earlier, you can access other fields in the COFF or PE header. The next snippet illustrates this for Subsystem (as above) plus three additional fields with their respective offsets.
NOTE: To reduce clutter, the enum declarations used in the following can be found here
var p = GetModuleHandleW(default); // PE image VM mapped base address
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
var subsys = (Subsystem)Marshal.ReadInt16(p + 0x005C); // (same as before)
var machine = (ImageFileMachine)Marshal.ReadInt16(p + 0x0004); // new
var imgType = (ImageFileCharacteristics)Marshal.ReadInt16(p + 0x0016); // new
var dllFlags = (DllCharacteristics)Marshal.ReadInt16(p + 0x005E); // new
// ... etc.
To improve things when accessing multiple fields in unmanaged memory, it's essential to define an overlaying struct. This allows for direct and natural managed access using C#. For the running example, I merged the adjacent COFF and PE headers together into the following C# struct definition, and only included the four fields we deemed interesting:
[StructLayout(LayoutKind.Explicit)]
struct COFF_PE
{
[FieldOffset(0x04)] public ImageFileMachine MachineType;
[FieldOffset(0x16)] public ImageFileCharacteristics Characteristics;
[FieldOffset(0x5C)] public Subsystem Subsystem;
[FieldOffset(0x5E)] public DllCharacteristics DllCharacteristics;
};
NOTE: A fuller version of this struct, without the omitted fields, can be found here
Any interop struct such as this has to be properly setup at runtime, and there are many options for doing so. Ideally, its generally better to impose the struct overlay "in-situ" directly on the unmanaged memory, so that no memory copying needs to occur. To avoid prolonging the discussion here even further however, I will instead show an easier method that does involve copying.
var p = GetModuleHandleW(default);
var _pe = Marshal.PtrToStructure<COFF_PE>(p + Marshal.ReadInt32(p, 0x3C));
Trace.WriteLine($#"
MachineType: {_pe.MachineType}
Characteristics: {_pe.Characteristics}
Subsystem: {_pe.Subsystem}
DllCharacteristics: {_pe.DllCharacteristics}");
4. Output of the demo code
Here is the output when a console program is running...
MachineType: Amd64
Characteristics: ExecutableImage, LargeAddressAware
Subsystem: WindowsCui (3)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware
...compared to GUI (WPF) application:
MachineType: Amd64
Characteristics: ExecutableImage, LargeAddressAware
Subsystem: WindowsGui (2)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware
[OLD: original answer from 2012...]
To determine if a .NET application is running in GUI mode:
bool is_console_app = Console.OpenStandardInput(1) != Stream.Null;
Environment.UserInteractive Property
If all you're trying to do is to determine whether the console will continue to exist after your program exits (so that you can, for example, prompt the user to hit Enter before the program exits), then all you have to do is to check if your process is the only one attached to the console. If it is, then the console will be destroyed when your process exits. If there are other processes attached to the console, then the console will continue to exist (because your program won't be the last one).
For example*:
using System;
using System.Runtime.InteropServices;
namespace CheckIfConsoleWillBeDestroyedAtTheEnd
{
internal class Program
{
private static void Main(string[] args)
{
// ...
if (ConsoleWillBeDestroyedAtTheEnd())
{
Console.WriteLine("Press any key to continue . . .");
Console.ReadKey();
}
}
private static bool ConsoleWillBeDestroyedAtTheEnd()
{
var processList = new uint[1];
var processCount = GetConsoleProcessList(processList, 1);
return processCount == 1;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint GetConsoleProcessList(uint[] processList, uint processCount);
}
}
(*) Adapted from code found here.
I haven't tested it, but Environment.UserInteractive looks promising.
A possible improvement of Glenn Slayden's solution:
bool isConsoleApplication = Console.In != StreamReader.Null;
To prompt for user input in an interactive console, but do nothing when run without a console or when input has been redirected:
if (Environment.UserInteractive && !Console.IsInputRedirected)
{
Console.ReadKey();
}

Categories

Resources