I have written a Windows Service in PowerShell. It works well, but there is one major limitation that I need to find a solution for. I need to list all processes with Windowed Applications - this is for passive application metering (I.E. Apps opened and in-use by each logged-on user).
The service runs as Local System. I can see all of the processes, but as the service is in the non-interactive desktop (Session ID 0) I cannot see the MainWindowTitle or MainWindowhandle, the titles are all null and the handles are all 0.
I have tried using Get-Process, Get-CIMInstance Win32_Process and [System.Diagnostics.Process]::GetProcesses(). None of these worked (I get all processes, but with data redacted).
I decided to create a C# console application that the PowerShell service will execute and collect the response from. This worked, but still excluded 'sensitive' information, so the critical property MainWindowhandle was always 0.
Here is the C# console app (it was a quick job to test only):
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Security.Permissions;
namespace ProcessManager
{
public class ProcessRecord
{
// Auto-Initialized properties
public string Name { get; set; }
public int MainWindowHandle { get; set; }
public string WindowTitle { get; set; }
public int SessionId { get; set; }
public DateTime StartTime { get; set; }
}
class Program
{
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
[HostProtectionAttribute(SecurityAction.LinkDemand, SharedState = true, Synchronization = true, ExternalProcessMgmt = true, SelfAffectingProcessMgmt = true)]
[PermissionSetAttribute(SecurityAction.InheritanceDemand, Name = "FullTrust")]
static void Main(string[] args)
{
listProcesses();
}
public static void listProcesses()
{
List<ProcessRecord> processesList = new List<ProcessRecord>{};
Process.GetProcesses().ToList().ForEach(p =>
{
try
{
processesList.Add(new ProcessRecord
{
Name = p.ProcessName,
MainWindowHandle = (int) p.MainWindowHandle,
WindowTitle = p.MainWindowTitle,
SessionId = p.SessionId,
StartTime = p.StartTime
});
}
catch (Win32Exception)
{
// Just ignoring this to avoid the Access Denied exception for low-level system processes
}
});
Console.WriteLine(JsonConvert.SerializeObject(processesList));
}
}
}
I tried running the service as a Local User in the local Administrators group. I also tried enabling 'Allow the service to interact with the desktop' out of desperation.
I do not need to know the MainWindowHandle, I just need to list processes where the MainWindowhandle is not 0. Unfortunately, I will need to know the session ID.
How should I proceed? Is the answer simple "It cannot be done" or is there a naughty workaround, such as impersonation?
Maybe there's an easier way to list applications opened by users without having to depend on MainWindowHandle != 0?
Thanks for any pointers!