GPAL - Generally Positive Automation Library
v1.0
GPAL The Fluent Automation LIbrary
Toggle main menu visibility
Loading...
Searching...
No Matches
DesktopGuard.cs
1
// =============================================================================
2
// GPAL - Generally Positive Automation Library
3
// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4
//
5
// This file is part of GPAL.
6
// Licensed under the Business Source License 1.1
7
//
8
// Primary development, architecture, and vision by Michael B. Vederman,
9
// CEO of Software Decisions, Inc., Texas.
10
//
11
// Internal development maintained privately.
12
// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13
//
14
// See LICENSE for full terms, including Additional Use Grant.
15
// =============================================================================
16
17
using
System;
18
using
System.Diagnostics;
19
using
System.IO;
20
using
static
GenerallyPositive
.
Enums
;
21
22
namespace
GenerallyPositive.Browser
23
{
38
internal
static
class
DesktopGuard
39
{
41
internal
const
string
ScriptName =
"GPALReturnToDefaultDesktop.ps1"
;
42
44
private
const
int
DefaultDeadlineInSeconds = 600;
45
46
// written next to the binary rather than kept as a file in the repo, so the copy that runs is always the
47
// copy this version of GPAL means and is always beside whatever exe used a hidden desktop
48
private
const
string
script =
@"param([int]$ParentPid = 0, [int]$DeadlineSeconds = 0)
49
50
# Puts the screen back on the Default desktop.
51
#
52
# GPAL writes this file next to the binary that used a hidden desktop, and runs it in the background while a
53
# peek is on, so a process that dies holding the screen still hands it back.
54
#
55
# Run it with no arguments to get a stranded screen back by hand. Nothing on a hidden desktop can launch it, so
56
# the way in is Ctrl+Alt+Del, Task Manager, File, Run new task, and the path to the .cmd beside this file.
57
58
Add-Type -Namespace GPAL -Name Desktops -MemberDefinition @'
59
[DllImport(""user32.dll"", SetLastError = true, CharSet = CharSet.Unicode)]
60
public static extern IntPtr OpenDesktop(string lpszDesktop, uint dwFlags, bool fInherit, uint dwDesiredAccess);
61
62
[DllImport(""user32.dll"", SetLastError = true)]
63
public static extern bool SwitchDesktop(IntPtr hDesktop);
64
65
[DllImport(""user32.dll"", SetLastError = true)]
66
public static extern bool CloseDesktop(IntPtr hDesktop);
67
'@
68
69
$DesktopSwitchDesktop = 0x0100
70
71
if ($ParentPid -gt 0) {
72
$deadline = (Get-Date).AddSeconds($(if ($DeadlineSeconds -gt 0) { $DeadlineSeconds } else { 600 }))
73
74
while ((Get-Date) -lt $deadline) {
75
if ($null -eq (Get-Process -Id $ParentPid -ErrorAction SilentlyContinue)) { break }
76
77
Start-Sleep -Milliseconds 500
78
}
79
}
80
81
# switching to the desktop already on the screen does nothing, so this is safe whether anything is stranded or not
82
$default = [GPAL.Desktops]::OpenDesktop('Default', 0, $false, $DesktopSwitchDesktop)
83
84
if ($default -ne [IntPtr]::Zero) {
85
[void][GPAL.Desktops]::SwitchDesktop($default)
86
[void][GPAL.Desktops]::CloseDesktop($default)
87
}
88
"
;
89
91
internal
static
string
Folder
92
{
93
get
{
return
AppDomain.CurrentDomain.BaseDirectory; }
94
}
95
101
internal
static
string
Write()
102
{
103
string
retVal =
null
;
104
string
path = Path.Combine(Folder, ScriptName);
105
106
// caught rather than thrown, because a folder that cannot be written to is a reason to peek without
107
// a guard and not a reason to fail the peek
108
try
109
{
110
File.WriteAllText(path, script);
111
112
retVal = path;
113
}
114
catch
(Exception ex)
115
{
116
GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Could not put the desktop recovery script in [{Folder}]"
,
null
, GPALObjectType.Browser, ex);
117
}
118
119
return
retVal;
120
}
121
128
internal
static
Process Start(
int
forMs)
129
{
130
Process retVal =
null
;
131
string
path = Write();
132
133
if
(
null
!= path)
134
{
135
int
seconds = 0 < forMs ? (forMs / 1000) + 30 : DefaultDeadlineInSeconds;
136
137
ProcessStartInfo starting =
new
ProcessStartInfo
138
{
139
FileName =
"powershell.exe"
,
140
Arguments = $
"-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden "
+
141
$
"-File \"{path}\" -ParentPid {Process.GetCurrentProcess().Id} -DeadlineSeconds {seconds}"
,
142
UseShellExecute =
false
,
143
CreateNoWindow =
true
,
144
};
145
146
// caught rather than thrown for the same reason Write catches: no guard is worse than a guard,
147
// and both are better than no peek
148
try
149
{
150
retVal = Process.Start(starting);
151
}
152
catch
(Exception ex)
153
{
154
GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Could not start the desktop guard from [{path}]"
,
null
, GPALObjectType.Browser, ex);
155
}
156
}
157
158
return
retVal;
159
}
160
166
internal
static
void
Stop(Process guard)
167
{
168
if
(
null
!= guard)
169
{
170
// caught rather than thrown because this runs on the way out of a peek, where the screen is
171
// already back and a stray powershell is not worth taking the caller down for
172
try
173
{
174
if
(
false
== guard.HasExited)
175
guard.Kill();
176
}
177
catch
(Exception ex)
178
{
179
GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
"Could not stop the desktop guard"
,
null
, GPALObjectType.Browser, ex);
180
}
181
182
guard.Dispose();
183
}
184
}
185
}
186
}
GenerallyPositive.Enums
Definition
Enums.cs:37
GenerallyPositive.Browser
Definition
Browser.cs:44
GenerallyPositive
Definition
Application.cs:32
Browser
DesktopGuard.cs
Generated by
1.17.0