Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows service created to shut down computer is not working when computer is locked

I have created a Windows service in C# to shut down the computer.

The service works fine when the computer is not locked (Ctrl + Alt + Del).

But some how it doesn't shut down when my computer is locked.

// call
DoExitWin(EWX_SHUTDOWN);

[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct TokPriv1Luid
{
    public int Count;
    public long Luid;
    public int Attr;
}

[DllImport("kernel32.dll", ExactSpelling = true)]
internal static extern IntPtr GetCurrentProcess();

[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
    ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
internal static extern bool ExitWindowsEx(int flg, int rea);

internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
internal const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
internal const int EWX_LOGOFF = 0x00000000;
internal const int EWX_SHUTDOWN = 0x00000001;
internal const int EWX_REBOOT = 0x00000002;
internal const int EWX_FORCE = 0x00000004;
internal const int EWX_POWEROFF = 0x00000008;
internal const int EWX_FORCEIFHUNG = 0x00000010;

private static void DoExitWin(int flg)
{
    bool ok;
    TokPriv1Luid tp;
    IntPtr hproc = GetCurrentProcess();
    IntPtr htok = IntPtr.Zero;
    ok = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
    tp.Count = 1;
    tp.Luid = 0;
    tp.Attr = SE_PRIVILEGE_ENABLED;
    ok = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid);
    ok = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero,IntPtr.Zero);
    ok = ExitWindowsEx(flg, 0);
}

UPDATE:

Based on help from Chris Haas I am trying to find which call returns an error based on the value of ok:

ok = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
int error = Marshal.GetLastWin32Error(); //error 87 ok return true 
ok = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid); 
error = Marshal.GetLastWin32Error(); //error 997 ok return true rest of ok true with zero error code

ok = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero,IntPtr.Zero);
ok = ExitWindowsEx(flg, 0); //This mean error in ok 

But ok returns true all the time.

like image 530
Rohit Avatar asked Oct 11 '22 05:10

Rohit


2 Answers

I'm guessing its a permission issue but I don't know that definitely. One very important thing is that you are throwing away potential errors. You keep setting the ok variable but you never check it. This would probably tell you where there's a problem

EDIT

Also, I think you actually want to pass EWX_SHUTDOWN | EWX_POWEROFF

EDIT 2

If you get an error you should call Marshal.GetLastWin32Error()

EDIT 3

You don't have to call GetLastError every time, just if ok is false:

int error;
ok = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
if(!ok){
    error = Marshal.GetLastWin32Error();
    throw new ApplicationException('Error : ' + error);
}
tp.Count = 1;
tp.Luid = 0;
tp.Attr = SE_PRIVILEGE_ENABLED;
ok = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tp.Luid);
if(!ok){
    error = Marshal.GetLastWin32Error();
    throw new ApplicationException('Error : ' + error);
}
ok = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero,IntPtr.Zero);
if(!ok){
    error = Marshal.GetLastWin32Error();
    throw new ApplicationException('Error : ' + error);
}
ok = ExitWindowsEx(flg, 0);
if(!ok){
    error = Marshal.GetLastWin32Error();
    throw new ApplicationException('Error : ' + error);
}
like image 76
Chris Haas Avatar answered Oct 18 '22 02:10

Chris Haas


Use instead
InitiateSystemShutdownEx. See the comments regarding the parameters, you can force a shutdown if the machine is locked.

EDIT: I remember I used it some time ago. There's a specific comment in MSDN:

*Windows Server 2003 and Windows XP: If the computer is locked and the bForceAppsClosed parameter is FALSE, the last error code is ERROR_MACHINE_LOCKED. If the system is not ready to handle the request, the last error code is ERROR_NOT_READY. The application should wait a short while and retry the call. For example, the system can be unready to initiate a shutdown, and return ERROR_NOT_READY, if the shutdown request comes at the same time a user tries to log onto the system. In this case, the application should wait a short while and retry the call.*

like image 2
Haplo Avatar answered Oct 18 '22 02:10

Haplo