Answering my own question as I think I have found why this was happening and how to fix it.
It would seem the issue was that, if my Launcher was opened with elevated privileges, then any process ran by it would also inherit them. This meant that my NamedPipeServerStream would sometimes be running with elevated permissions, but my NamedPipeClientStream would always run without them. Regardless of the access rules I set during their creation, my pipe Client would always refuse to connect to it.
Since I didn't need the game.exe to have elevated access, I looked into how to un-elevate a sub-process started by an elevated one, and ended up passing through explorer.exe as suggested in this blog post.
// If the Launcher has UAC, use the explorer to launch the game
if (FileUtility.HasElevatedPermissions())
{
ProcessStartInfo processInfo = new()
{
FileName = "explorer.exe",
UseShellExecute = true,
Arguments = $"\"{exePath}\""
};
Process.Start(processInfo);
}
// If the Launcher doesn't have UAC, open the game normally
else
{
ProcessStartInfo processInfo = new()
{
FileName = exePath,
UseShellExecute = true,
Verb = "open"
};
Process.Start(processInfo);
}
This seems to do the trick, game.exe always opens without privileges and the deeplinker.exe pipe can communicate without issue.
Thanks to both @Sinatr and @BenVoigt for their suggestions.
If anyone's interested in HasElevatedPermissions(), I try to create a directory under C:/Program Files and return whether I succeed or not.
public static bool HasElevatedPermissions(string _path = null)
{
if (string.IsNullOrEmpty(_path))
_path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
float random = UnityEngine.Random.Range(0f, 1f);
var path = @$"{_path}\temp{random}"; // Random to prevent user interference
try
{
Directory.CreateDirectory(path);
}
catch (UnauthorizedAccessException)
{
return false;
}
Directory.Delete(path);
return true;
}
Not the best approach I admit, but I couldn't find anything that could easily work from a Unity program.