Related
I am trying to make use of RegenerateUserEnvironment method so that I can refresh environment variables without restarting the instance. However, it ends up with an access violation error. I am not sure but to me, a process updating its own memory should not cause an error.
Things I tried:
Passing the actual token of current process. No chance.
Duplicating current token with all possible permissions. No chance.
Running as administrator, in case it is about SeDebugPrivilege or similar, but no chance.
Any ideas?
Code:
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32;
namespace EnvVarControl
{
internal static partial class Program
{
#region Pinvoke
public const uint STANDARD_RIGHTS_READ = 0x00020000;
public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
public const uint TOKEN_ADJUST_DEFAULT = 0x0080;
public const uint TOKEN_ADJUST_GROUPS = 0x0040;
public const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
public const uint TOKEN_ADJUST_SESSIONID = 0x0100;
public const uint TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID;
public const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
public const uint TOKEN_DUPLICATE = 0x0002;
public const uint TOKEN_IMPERSONATE = 0x0004;
public const uint TOKEN_QUERY = 0x0008;
public const uint TOKEN_QUERY_SOURCE = 0x0010;
public const uint TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY;
public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation = 2
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int Length;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("advapi32.dll", SetLastError = true, EntryPoint = "DuplicateTokenEx")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, TOKEN_TYPE TokenType, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
[LibraryImport("shell32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool RegenerateUserEnvironment(IntPtr hToken,
[MarshalAs(UnmanagedType.Bool)] bool bSet);
#endregion Pinvoke
public static void Main()
{
// Subscribe to the UserPreferenceChanged event
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static void CheckPathBeforeAndAfterRegenerateUserEnvironment()
{
// Get the value of the PATH environment variable before calling RegenerateUserEnvironment
var pathBefore = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"BEFORE: {pathBefore}\n");
RefreshEnvironmentVariables();
// Get the value of the PATH environment variable after calling RegenerateUserEnvironment
var pathAfter = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"AFTER: {pathAfter}\n");
// Compare the two values of the PATH environment variable
if (pathBefore != pathAfter)
{
Console.WriteLine("Warning: The value of the PATH environment variable has changed!");
}
else
{
Console.WriteLine("No change.");
}
}
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
// Check if the environment variables have changed
if (e.Category == UserPreferenceCategory.General)
{
// Check the value of the PATH environment variable before and after calling RegenerateUserEnvironment
CheckPathBeforeAndAfterRegenerateUserEnvironment();
}
}
private static void RefreshEnvironmentVariables()
{
// Open the process token and duplicate
var token = WindowsIdentity.GetCurrent().AccessToken.DangerousGetHandle();
var duplicatededToken = DuplicateToken(token);
// Call the RegenerateUserEnvironment function to update the environment variables
if (!RegenerateUserEnvironment(duplicatededToken, true))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
}
private static nint DuplicateToken(nint token)
{
var sa = new SECURITY_ATTRIBUTES
{
bInheritHandle = false
};
sa.Length = Marshal.SizeOf(sa);
sa.lpSecurityDescriptor = 0;
const TOKEN_TYPE TokenType = TOKEN_TYPE.TokenPrimary;
const SECURITY_IMPERSONATION_LEVEL SecurityImpersonation = SECURITY_IMPERSONATION_LEVEL.SecurityIdentification;
var DupedToken = new IntPtr(0);
if (!DuplicateTokenEx(token, TOKEN_ALL_ACCESS, ref sa, TokenType, SecurityImpersonation, ref DupedToken))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
return DupedToken;
}
}
}
Error message:
Exception thrown at ... (shell32.dll) in EnvVarControl.exe: ...: Access violation writing location ...
Edit: Based on the very accurate answer, I paste the correct piece of code below:
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace EnvVarControl
{
internal static partial class Program
{
#region Pinvoke
[LibraryImport("shell32.dll", SetLastError = true)]
[UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool RegenerateUserEnvironment(out IntPtr pPrevEnv, [MarshalAs(UnmanagedType.Bool)] bool bSetCurrentEnv);
#endregion Pinvoke
public static void Main()
{
// Subscribe to the UserPreferenceChanged event
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static void CheckPathBeforeAndAfterRegenerateUserEnvironment()
{
// Get the value of the PATH environment variable before calling RegenerateUserEnvironment
var pathBefore = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"BEFORE: {pathBefore}\n");
RefreshEnvironmentVariables();
// Get the value of the PATH environment variable after calling RegenerateUserEnvironment
var pathAfter = Environment.GetEnvironmentVariable("PATH");
Console.WriteLine($"AFTER: {pathAfter}\n");
// Compare the two values of the PATH environment variable
if (pathBefore != pathAfter)
{
Console.WriteLine("Warning: The value of the PATH environment variable has changed!");
}
else
{
Console.WriteLine("No change.");
}
}
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
// Check if the environment variables have changed
if (e.Category == UserPreferenceCategory.General)
{
// Check the value of the PATH environment variable before and after calling RegenerateUserEnvironment
CheckPathBeforeAndAfterRegenerateUserEnvironment();
}
}
private static void RefreshEnvironmentVariables()
{
// Call the RegenerateUserEnvironment function to update the environment variables
// Reference: https://stackoverflow.com/questions/74913727/trying-to-create-a-demo-using-regenerateuserenvironment-throwing-access-violatio/74914038#74914038
if (!RegenerateUserEnvironment(out _, true))
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
RegenerateUserEnvironment does not need a token; the first parameter is a 2-level pointer, which will be set to the previous environmet block.
define RegenerateUserEnvironment as:
[DllImport("shell32.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool RegenerateUserEnvironment(out IntPtr pPrevEnv, [MarshalAs(UnmanagedType.Bool)] bool bSetCurrentEnv);
and call it like follows:
RegenerateUserEnvironment(out IntPtr pPrevEnv, true)
I'm trying to write a certificate manager, and I want to manage the permissions over the certificate file. I'd prefer not reinventing the wheel of the Windows permissions dialog, so ideally there would be some kind of shell command that I could pass the path of the item whose permissions are being managed. Then, I could just invoke it and let the shell take care of updating the permissions.
I've seen some mention here and there of a shell function, SHObjectProperties, but nothing clear on how to use it. Any help would be appreciated.
You can display the Windows file permissions dialog using ShellExecuteEx (using the "properties" verb and the "Security" parameter).
This will display a dialog like the following within your process, and the file permission viewing and editing will be fully functional just as if you had got this dialog through the Windows explorer shell:
Here is an example with Windows Forms where a file is selected and the Security properties of that file then displayed. I have used the P/Invoke code for ShellExecuteEx from this Stackoverflow answer.
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FileSecurityProperties
{
public partial class FileSelectorForm : Form
{
private static bool ShowFileSecurityProperties(string Filename, IntPtr parentHandle)
{
SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
info.lpVerb = "properties";
info.lpFile = Filename;
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
info.hwnd = parentHandle;
info.lpParameters = "Security"; // Opens the file properties on the Security tab
return ShellExecuteEx(ref info);
}
private void fileSelectButton_Click(object sender, EventArgs e)
{
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
ShowFileSecurityProperties(
openFileDialog.FileName,
this.Handle); // Pass parent window handle for properties dialog
}
}
#region P/Invoke code for ShellExecuteEx from https://stackoverflow.com/a/1936957/4486839
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SHELLEXECUTEINFO
{
public int cbSize;
public uint fMask;
public IntPtr hwnd;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpVerb;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpParameters;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpDirectory;
public int nShow;
public IntPtr hInstApp;
public IntPtr lpIDList;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpClass;
public IntPtr hkeyClass;
public uint dwHotKey;
public IntPtr hIcon;
public IntPtr hProcess;
}
private const int SW_SHOW = 5;
private const uint SEE_MASK_INVOKEIDLIST = 12;
#endregion
#region Irrelevant Windows forms code
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
this.fileSelectButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// openFileDialog1
//
this.openFileDialog.FileName = "";
//
// fileSelectButton
//
this.fileSelectButton.Location = new System.Drawing.Point(52, 49);
this.fileSelectButton.Name = "fileSelectButton";
this.fileSelectButton.Size = new System.Drawing.Size(131, 37);
this.fileSelectButton.TabIndex = 0;
this.fileSelectButton.Text = "Select file ...";
this.fileSelectButton.UseVisualStyleBackColor = true;
this.fileSelectButton.Click += new System.EventHandler(this.fileSelectButton_Click);
//
// FileSelectorForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(248, 162);
this.Controls.Add(this.fileSelectButton);
this.Name = "FileSelectorForm";
this.Text = "File Selector";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.OpenFileDialog openFileDialog;
private System.Windows.Forms.Button fileSelectButton;
public FileSelectorForm()
{
InitializeComponent();
}
#endregion
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FileSelectorForm());
}
}
}
If you were hoping to get the file permissions dialog on its own, rather than as a tab in the general file properties dialog, that is possible using aclui.dll, e.g. using the EditSecurity function, but this will NOT then give you your other requirement of having the file permissions handling taken care of for you, because you have to provide an interface which does the getting and setting of security properties if you go down that route, and it looks like a lot of coding.
Here is a utilily class that allows you to have only the security property sheet (not all sheets the shell displays).
You can call it like this in a console app:
class Program
{
[STAThread]
static void Main(string[] args)
{
// NOTE: if the dialog looks old fashioned (for example if used in a console app),
// then add an app.manifest and uncomment the dependency section about Microsoft.Windows.Common-Controls
PermissionDialog.Show(IntPtr.Zero, #"d:\temp\killroy_was_here.png");
}
}
or like this in a winform app
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
PermissionDialog.Show(IntPtr.Zero, #"d:\temp\killroy_was_here.png");
}
}
And this is the main class. It's basically using the same thing as the shell, but in its own property sheet.
public static class PermissionDialog
{
public static bool Show(IntPtr hwndParent, string path)
{
if (path == null)
throw new ArgumentNullException("path");
SafePidlHandle folderPidl;
int hr;
hr = SHILCreateFromPath(Path.GetDirectoryName(path), out folderPidl, IntPtr.Zero);
if (hr != 0)
throw new Win32Exception(hr);
SafePidlHandle filePidl;
hr = SHILCreateFromPath(path, out filePidl, IntPtr.Zero);
if (hr != 0)
throw new Win32Exception(hr);
IntPtr file = ILFindLastID(filePidl);
System.Runtime.InteropServices.ComTypes.IDataObject ido;
hr = SHCreateDataObject(folderPidl, 1, new IntPtr[] { file }, null, typeof(System.Runtime.InteropServices.ComTypes.IDataObject).GUID, out ido);
if (hr != 0)
throw new Win32Exception(hr);
// if you get a 'no such interface' error here, make sure the running thread is STA
IShellExtInit sei = (IShellExtInit)new SecPropSheetExt();
sei.Initialize(IntPtr.Zero, ido, IntPtr.Zero);
IShellPropSheetExt spse = (IShellPropSheetExt)sei;
IntPtr securityPage = IntPtr.Zero;
spse.AddPages((p, lp) =>
{
securityPage = p;
return true;
}, IntPtr.Zero);
PROPSHEETHEADER psh = new PROPSHEETHEADER();
psh.dwSize = Marshal.SizeOf(psh);
psh.hwndParent = hwndParent;
psh.nPages = 1;
psh.phpage = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(psh.phpage, securityPage);
// TODO: adjust title & icon here, also check out the available flags
psh.pszCaption = "Permissions for '" + path + "'";
IntPtr res;
try
{
res = PropertySheet(ref psh);
}
finally
{
Marshal.FreeHGlobal(psh.phpage);
}
return res == IntPtr.Zero;
}
private class SafePidlHandle : SafeHandle
{
public SafePidlHandle()
: base(IntPtr.Zero, true)
{
}
public override bool IsInvalid
{
get { return handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
{
if (IsInvalid)
return false;
Marshal.FreeCoTaskMem(handle);
return true;
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct PROPSHEETHEADER
{
public int dwSize;
public int dwFlags;
public IntPtr hwndParent;
public IntPtr hInstance;
public IntPtr hIcon;
public string pszCaption;
public int nPages;
public IntPtr nStartPage;
public IntPtr phpage;
public IntPtr pfnCallback;
}
[DllImport("shell32.dll")]
private static extern IntPtr ILFindLastID(SafePidlHandle pidl);
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern int SHILCreateFromPath(string pszPath, out SafePidlHandle ppidl, IntPtr rgflnOut);
[DllImport("shell32.dll")]
private static extern int SHCreateDataObject(SafePidlHandle pidlFolder, int cidl, IntPtr[] apidl, System.Runtime.InteropServices.ComTypes.IDataObject pdtInner, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out System.Runtime.InteropServices.ComTypes.IDataObject ppv);
[DllImport("comctl32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr PropertySheet(ref PROPSHEETHEADER lppsph);
private delegate bool AddPropSheetPage(IntPtr page, IntPtr lParam);
[ComImport]
[Guid("1f2e5c40-9550-11ce-99d2-00aa006e086c")] // this GUID points to the property sheet handler for permissions
private class SecPropSheetExt
{
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E8-0000-0000-C000-000000000046")]
private interface IShellExtInit
{
void Initialize(IntPtr pidlFolder, System.Runtime.InteropServices.ComTypes.IDataObject pdtobj, IntPtr hkeyProgID);
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214E9-0000-0000-C000-000000000046")]
private interface IShellPropSheetExt
{
void AddPages([MarshalAs(UnmanagedType.FunctionPtr)] AddPropSheetPage pfnAddPage, IntPtr lParam);
void ReplacePage(); // not fully defined, we don't use it
}
}
Opps my bad 'attrib' is how it used to be.
What you are looking to do I believe is "change security descriptors on folders and files". The command line tool is cacls.
Please see: http://en.wikipedia.org/wiki/Cacls
I'm trying to set an already installed windows service to automatic delayed start in C#. How do I set a windows service to
Automatic (Delayed Start)
Can't find that value in the ServiceStartMode enum.
Edit:1
public class ServiceAutoStartInfo
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool fDelayedAutostart;
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig2(IntPtr hService, int dwInfoLevel, IntPtr lpInfo);
// Service configuration parameter
const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3;
public bool ChangeDelayedAutoStart(IntPtr hService, bool delayed)
{
// Validate service handle
if (hService != IntPtr.Zero)
{
// Create
SERVICE_DELAYED_AUTO_START_INFO info = new SERVICE_DELAYED_AUTO_START_INFO();
// Set the DelayedAutostart property
info.fDelayedAutostart = delayed;
// Allocate necessary memory
IntPtr hInfo = Marshal.AllocHGlobal(Marshal.SizeOf(
typeof(SERVICE_DELAYED_AUTO_START_INFO)));
// Convert structure to pointer
Marshal.StructureToPtr(info, hInfo, true);
// Change the configuration
bool result = ChangeServiceConfig2(hService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, hInfo);
// Release memory
Marshal.FreeHGlobal(hInfo);
return result;
}
return false;
}
}
This is how I call it:
var controller = new ServiceController(s.ServiceName);
var autoDelay = new ServiceAutoStartInfo();
autoDelay.ChangeDelayedAutoStart(controller.ServiceHandle.DangerousGetHandle(), true);
But with no result.
Look into calling the Windows ChangeServiceConfig2 function, with dwInfoLevel of SERVICE_CONFIG_DELAYED_AUTO_START_INFO and a SERVICE_DELAYED_AUTO_START_INFO struct with fDelayedAutostart set to TRUE.
Or, you can do this with the command line:
sc.exe config <servicename> start= delayed-auto
I'm using the following, which works for me on Windows 7 (when run as admin):
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.ServiceProcess;
namespace ServiceManager
{
/// <summary>
/// Extensions to the ServiceController class.
/// </summary>
public static class ServiceControlerExtensions
{
/// <summary>
/// Set the start mode for the service.
/// </summary>
/// <param name="serviceController">The service controller.</param>
/// <param name="mode">The desired start mode.</param>
public static void SetStartMode(this ServiceController serviceController, ServiceStartModeEx mode)
{
IntPtr serviceManagerHandle = OpenServiceManagerHandle();
IntPtr serviceHandle = OpenServiceHandle(serviceController, serviceManagerHandle);
try
{
if (mode == ServiceStartModeEx.DelayedAutomatic)
{
ChangeServiceStartType(serviceHandle, ServiceStartModeEx.Automatic);
ChangeDelayedAutoStart(serviceHandle, true);
}
else
{
// Delayed auto-start overrides other settings, so it must be set first.
ChangeDelayedAutoStart(serviceHandle, false);
ChangeServiceStartType(serviceHandle, mode);
}
}
finally
{
if (serviceHandle != IntPtr.Zero)
{
CloseServiceHandle(serviceHandle);
}
if (serviceHandle != IntPtr.Zero)
{
CloseServiceHandle(serviceManagerHandle);
}
}
}
private static IntPtr OpenServiceHandle(ServiceController serviceController, IntPtr serviceManagerHandle)
{
var serviceHandle = OpenService(
serviceManagerHandle,
serviceController.ServiceName,
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG);
if (serviceHandle == IntPtr.Zero)
{
throw new ExternalException("Open Service Error");
}
return serviceHandle;
}
private static IntPtr OpenServiceManagerHandle()
{
IntPtr serviceManagerHandle = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
if (serviceManagerHandle == IntPtr.Zero)
{
throw new ExternalException("Open Service Manager Error");
}
return serviceManagerHandle;
}
private static void ChangeServiceStartType(IntPtr serviceHandle, ServiceStartModeEx mode)
{
bool result = ChangeServiceConfig(
serviceHandle,
SERVICE_NO_CHANGE,
(uint)mode,
SERVICE_NO_CHANGE,
null,
null,
IntPtr.Zero,
null,
null,
null,
null);
if (result == false)
{
ThrowLastWin32Error("Could not change service start type");
}
}
private static void ChangeDelayedAutoStart(IntPtr hService, bool delayed)
{
// Create structure that contains DelayedAutoStart property.
SERVICE_DELAYED_AUTO_START_INFO info = new SERVICE_DELAYED_AUTO_START_INFO();
// Set the DelayedAutostart property in that structure.
info.fDelayedAutostart = delayed;
// Allocate necessary memory.
IntPtr hInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SERVICE_DELAYED_AUTO_START_INFO)));
// Convert structure to pointer.
Marshal.StructureToPtr(info, hInfo, true);
// Change the configuration.
bool result = ChangeServiceConfig2(hService, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, hInfo);
// Release memory.
Marshal.FreeHGlobal(hInfo);
if (result == false)
{
ThrowLastWin32Error("Could not set service to delayed automatic");
}
}
private static void ThrowLastWin32Error(string messagePrefix)
{
int nError = Marshal.GetLastWin32Error();
var win32Exception = new Win32Exception(nError);
string message = string.Format("{0}: {1}", messagePrefix, win32Exception.Message);
throw new ExternalException(message);
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode,
SetLastError = true)]
private static extern IntPtr OpenSCManager(
string machineName,
string databaseName,
uint dwAccess);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern Boolean ChangeServiceConfig(
IntPtr hService,
UInt32 nServiceType,
UInt32 nStartType,
UInt32 nErrorControl,
String lpBinaryPathName,
String lpLoadOrderGroup,
IntPtr lpdwTagId,
[In] char[] lpDependencies,
String lpServiceStartName,
String lpPassword,
String lpDisplayName);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig2(
IntPtr hService,
int dwInfoLevel,
IntPtr lpInfo);
[DllImport("advapi32.dll", EntryPoint = "CloseServiceHandle")]
private static extern int CloseServiceHandle(IntPtr hSCObject);
private const uint SERVICE_NO_CHANGE = 0xFFFFFFFF;
private const uint SERVICE_QUERY_CONFIG = 0x00000001;
private const uint SERVICE_CHANGE_CONFIG = 0x00000002;
private const uint SC_MANAGER_ALL_ACCESS = 0x000F003F;
private const int SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SERVICE_DELAYED_AUTO_START_INFO
{
public bool fDelayedAutostart;
}
}
}
namespace ServiceManager
{
public enum ServiceStartModeEx
{
Automatic = 2,
Manual = 3,
Disabled = 4,
DelayedAutomatic = 99
}
}
You call it like this:
var serviceController = new ServiceController("Windows Update");
try
{
serviceController.SetStartMode(ServiceStartModeEx.DelayedAutomatic);
}
finally
{
serviceController.Close();
}
Update: This only works for setting up new services and is not what the OP asked for:
You can use the DelayedAutoStart property of the ServiceInstaller.
installer.DelayedAutoStart = true;
I believe you need to combine both methods ChangeServiceConfig and ChangeServiceConfig2.
pseudo-code follows:
public static void ChangeServiceStartupType(ServiceStartupType type, ...)
{
if (type == AutomaticDelayed)
{
if (ChangeServiceConfig2(.., DelayedAutoStart, ..))
{
ChangeServiceConfig(.., Automatic, ..);
}
}
else
{
ChangeServiceConfig2(.., !DelayedAutoStart, ..)
ChangeServiceConfig(.., type, ..)
}
}
edit: you also need to remove "delayed-automatic" when requesting non-delayed startup-type. Otherwise it won't be possible to set "automatic" type. ("automatic-delayed" overrides "automatic")
SERVICE_DELAYED_AUTO_START_INFO structure documentation states:
fDelayedAutostart
If this member is TRUE, the service is started after other auto-start
services are started plus a short delay. Otherwise, the service is
started during system boot.
This setting is ignored unless the service is an auto-start service.
https://learn.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_delayed_auto_start_info?redirectedfrom=MSDN#members
So I think if the service is not auto-start it won't change it to auto delayed, you would have to use sc.exe
I am trying to P/Invoke the NotifyServiceStatusChange event in C# to check when a service has stopped. I managed to get it to compile and run without any errors, but now, when I stop the service, it doesn't seem to want to notify that its dead. Any ideas why that could be? You can test it out by copying this code into a blank console application; just be sure to replace "My Service Name" with your service name (there are two instances of this string below).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
public delegate void StatusChanged(IntPtr parameter);
public class SERVICE_NOTIFY : MarshalByRefObject
{
public uint dwVersion;
public StatusChanged pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
public struct SERVICE_STATUS_PROCESS {
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, ref IntPtr pNotifyBuffer);
public static SERVICE_NOTIFY notify;
public static GCHandle notifyHandle;
public static IntPtr unmanagedNotifyStructure;
static void Main(string[] args)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, "My Service Name", (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChanged changeDelegate = ReceivedStatusChangedEvent;
notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = changeDelegate;
notify.pContext = IntPtr.Zero;
notify.dwNotificationStatus = 0;
SERVICE_STATUS_PROCESS process;
process.dwServiceType = 0;
process.dwCurrentState = 0;
process.dwControlsAccepted = 0;
process.dwWin32ExitCode = 0;
process.dwServiceSpecificExitCode = 0;
process.dwCheckPoint = 0;
process.dwWaitHint = 0;
process.dwProcessId = 0;
process.dwServiceFlags = 0;
notify.ServiceStatus = process;
notify.dwNotificationTriggered = 0;
notify.pszServiceNames = Marshal.StringToHGlobalUni("My Service Name");
notifyHandle = GCHandle.Alloc(notify);
unmanagedNotifyStructure = Marshal.AllocHGlobal((IntPtr)(notifyHandle));
NotifyServiceStatusChange(hService, (uint)0x00000001, ref unmanagedNotifyStructure);
Console.WriteLine("Waiting for the service to stop. Press enter to exit.");
Console.ReadLine();
}
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
Console.WriteLine("Service stopped.");
}
}
}
If you want to keep the functionality you are attempting at present, you will need to multithread.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll", EntryPoint = "SleepEx")]
public static extern uint SleepEx(uint dwMilliseconds, [MarshalAsAttribute(UnmanagedType.Bool)] bool bAlertable);
public static SERVICE_NOTIFY notify;
public static GCHandle notifyHandle;
public static IntPtr unmanagedNotifyStructure;
static void Main(string[] args)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, "Apache2.2", (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChanged changeDelegate = ReceivedStatusChangedEvent;
notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.pContext = IntPtr.Zero;
notify.dwNotificationStatus = 0;
SERVICE_STATUS_PROCESS process;
process.dwServiceType = 0;
process.dwCurrentState = 0;
process.dwControlsAccepted = 0;
process.dwWin32ExitCode = 0;
process.dwServiceSpecificExitCode = 0;
process.dwCheckPoint = 0;
process.dwWaitHint = 0;
process.dwProcessId = 0;
process.dwServiceFlags = 0;
notify.ServiceStatus = process;
notify.dwNotificationTriggered = 0;
notify.pszServiceNames = Marshal.StringToHGlobalUni("Apache2.2");
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
unmanagedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, unmanagedNotifyStructure);
GC.KeepAlive(changeDelegate);
Console.WriteLine("Waiting for the service to stop. Press enter to exit.");
while (true)
{
try
{
string keyIn = Reader.ReadLine(500);
break;
}
catch (TimeoutException)
{
SleepEx(100,true);
}
}
notifyHandle.Free();
}
}
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void StatusChanged(IntPtr parameter);
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
Console.WriteLine("Service stopped.");
}
}
}
class Reader
{
private static Thread inputThread;
private static AutoResetEvent getInput, gotInput;
private static string input;
static Reader()
{
inputThread = new Thread(reader);
inputThread.IsBackground = true;
inputThread.Start();
getInput = new AutoResetEvent(false);
gotInput = new AutoResetEvent(false);
}
private static void reader()
{
while (true)
{
getInput.WaitOne();
input = Console.ReadLine();
gotInput.Set();
}
}
public static string ReadLine(int timeOutMillisecs)
{
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
return input;
else
throw new TimeoutException("User did not provide input within the timelimit.");
}
}
Read related question here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/f68fb826-036a-4b9c-81e6-4cbd87931feb/notifyservicestatuschange-not-working-in-c-for-windows-service-notification
Important quote: "the system invokes the specified callback function as an asynchronous procedure call (APC) queued to the calling thread. The calling thread must enter an alertable wait"
I don't remember whether .NET framework 4 uses alertable waiting when you enter Thread.Sleep or some form of Wait on waithandles, even though it uses alertable waiting for asynchronous I/O, for internal timer threads etc.
However just try Thread.Sleep or some flavor of Wait on some waithandle, instead of Console.ReadLine, make sure that your thread is blocked by those APIs at the time when you kill the service. This might do the magic - but, to my knowledge, this is a dangerous way, because .NET runtime does not expect user code to be executed on an APC. At least, try not to use NET framework resources or absolutely any APIs (especially synchronization-related or memory allocation) directly from your callback - just set some primitive variable and quit.
With APCs, the safest solution for you would be to have the callback implemented in some kind of native module, and also scheduled from some non-.NET thread, interoperating with managed code through shared variables, a pipe, or COM interface.
Or, as Hans Passant suggested in another copy of your question, just do polling from managed code. Absolutely safe, easy to implement, guaranteed to work.
Excellent source of relevant information is Joe Duffy's book (he covers a lot of topics, and alertable waits and .NET in particular): http://www.amazon.com/Concurrent-Programming-Windows-Joe-Duffy/dp/032143482X
UPDATE: Just consulted with Joe Duffy's book, yes indeed, scheduling .NET code on an APC may result in deadlocks, access violations and generally unpredictable behavior. So the answer is simple: don't do APC from a managed thread.
Simplified a lot of this from #Motes' answer...(EDIT: I put it into a class that people can use to easily wait for a service to stop; it will block!
Edit Again: Made sure this worked if you force garbage collection with GC.Collect() anywhere in the function...turns out, you DO need the SERVICE_STATUS_PROCESS.
Another Edit: Made sure it works if you abort your thread (note on that: can't abort sleeping threads so if you plan to abort this thread...then make sure you give it a timeout at least so the finalizer can run after the timeout hits), also added timeouts. Also ensured mapping 1-to-1 of the OS thread to the current .NET thread.
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ServiceAssistant
{
class ServiceHelper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll")]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll")]
static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("advapi32.dll")]
static extern bool CloseServiceHandle(IntPtr hSCObject);
delegate void StatusChangedCallbackDelegate(IntPtr parameter);
/// <summary>
/// Block until a service stops, is killed, or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
/// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
{
// Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
Thread.BeginThreadAffinity();
GCHandle notifyHandle = default(GCHandle);
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
SleepEx(timeout, true);
}
}
}
finally
{
// Clean up at the end of our operation, or if this thread is aborted.
if (hService != IntPtr.Zero)
{
CloseServiceHandle(hService);
}
if (hSCM != IntPtr.Zero)
{
CloseServiceHandle(hSCM);
}
GC.KeepAlive(changeDelegate);
if (notifyHandle != default(GCHandle))
{
notifyHandle.Free();
}
Thread.EndThreadAffinity();
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
}
}
}
Yes! We did it. What a journey it has been.
I want to look in the list of processes to determine if OpenOffice Calc is running or if OpenOffice Writer is running. With QuickStart off, I get a scalc.exe and an swriter.exe so its simple.
However when the quick start is on I just get soffice.bin and soffice.exe
Is there a way of asking those processes which application is running?
I think there is no way around checking the window title. You would have to enumerate all windows and check whether the title ends with "- OpenOffice.org Writer" or "- OpenOffice.org Calc" etc.
The following short sample would check whether Writer is running:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Sample
{
public class Window
{
public string Title { get; set; }
public int Handle { get; set; }
public string ProcessName { get; set; }
}
public class WindowHelper
{
/// <summary>
/// Win32 API Imports
/// </summary>
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder title, int size);
[DllImport("user32.dll")]
private static extern int EnumWindows(EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
public delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
private List<Window> _openOfficeWindows;
public List<Window> GetOpenOfficeWindows()
{
_openOfficeWindows = new List<Window>();
EnumWindowsProc ewp = new EnumWindowsProc(EvalWindow);
EnumWindows(ewp, 0);
return _openOfficeWindows;
}
private bool EvalWindow(IntPtr hWnd, int lParam)
{
if (!IsWindowVisible(hWnd))
return (true);
uint lpdwProcessId;
StringBuilder title = new StringBuilder(256);
GetWindowThreadProcessId(hWnd, out lpdwProcessId);
GetWindowText(hWnd, title, 256);
Process p = Process.GetProcessById((int)lpdwProcessId);
if (p != null && p.ProcessName.ToLower().Contains("soffice"))
{
_openOfficeWindows.Add(new Window() { Title = title.ToString(), Handle = hWnd.ToInt32(), ProcessName = p.ProcessName });
}
return (true);
}
}
class Program
{
static void Main(string[] args)
{
WindowHelper helper = new WindowHelper();
List<Window> openOfficeWindows = helper.GetOpenOfficeWindows();
foreach (var item in openOfficeWindows)
{
if (item.Title.EndsWith("- OpenOffice.org Writer"))
{
Console.WriteLine("Writer is running");
}
}
}
}
}