-
Configure WSUS/GPO programmatically
Posted on June 22nd, 2019 No commentsWe have a few hundred Windows PCs across the country. It is essential to keep the OS up to date, just think of the new BlueKeep vulnerability. These PCs are running 7/24 unattended, so we need to automate almost everything, including the Windows Update configuration. These PCs are completely separate from the intranet and do not connect to the Windows Domain, but they have to use the company’s WSUS servers. Each computer had the WSUS server pre-configured using the Local Group Policy (gpedit.msc), but after a few years we had to change the server address. Each PC is supervised by our custom management software. It was obvious that the WSUS server setting should be deployed through the same management system, as well as other settings.
At first I thougth I could easily change it in the Registry because there are many Windows settings stored there. I found the WSUS settings in many places in the Registry, but changing them didn’t help. Or rather – as it turned out later – Registry change is only half of the story. The GPO must be explicitly saved using a COM object so that the change can take effect. Fortunately, Group Policy API exists to perform this task. Everything we need is in the gpedit.h file.
The IGroupPolicy is designed specifically to modify a GPO directly. These are the main tasks:
- Open GPO for the computer and load registry information: IGroupPolicyObject::OpenLocalMachineGPO()
- Obtain the root registry handle for the computer section: IGroupPolicyObject::GetRegistryKey()
- Open the registry key, change value then close: RegOpenKeyEx(), RegSetValueEx(), RegCloseKey()
- Save the changes and update GPO: IGroupPolicyObject::Save()
The registry location for the Windows Update: Software\Policies\Microsoft\Windows\WindowsUpdate
The registry key for the WSUS server: WUServerThe original article about modification of Group Policy Objects can be found here:
More info about the IGroupPolicyObject interface on the Windows Dev Center
I present here three examples in three different languages: C++, C# .NET and Delphi.
The C++ version is the simplest because COM is supported by the Windows SDK.
The C# .NET version is a bit tricky, uses unmanaged code and ComImport. Although there is a GPMC library (Microsoft.GroupPolicy.Management.Interop) under .NET, but this library doesn’t have the MSIL version, only the pre-compiled x64 or x86 verions exist after installing GPMC. Installing GPMC alone poses a challenge, different operating systems require different methods. It’s too complicated. I was looking for an out-of-box solution that doesn’t require more software to install. GPMC isn’t like that.
You can read more here: GroupPolicyObject Class
The Delphi version is a mixture of the C++/C# versions: it has the declaration for the GPO interface then uses CoCreateInstance to obtain the GPO object. Thanks for the TRegistry unit, the code remained compact.
Solutions have been tested on Windows 7 and Windows 10 systems.
You can use this method to change other Windows Update settings or other Group Policy settings.
C++ Version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72#include "stdafx.h"
#define INITGUID
#include <windows.h>
#include <gpedit.h>
#include <iostream>
using namespace std;
#define CLSID_PolicySnapinUser { 0x0F6B957E, 0x509E, 0x11D1, 0xA7, 0xCC, 0x00, 0x00, 0xF8, 0x75, 0x71, 0xE3 }
int main()
{
wchar_t* wsusServer = L"http://127.0.0.1";
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
cout << "CoInit failed";
return 1;
}
IGroupPolicyObject* gpo = nullptr;
hr = CoCreateInstance(CLSID_GroupPolicyObject, nullptr, CLSCTX_INPROC_SERVER, IID_IGroupPolicyObject, (LPVOID *)&gpo);
if (FAILED(hr))
{
cout << "CoCrate failed";
return 1;
}
hr = gpo->OpenLocalMachineGPO(GPO_OPEN_LOAD_REGISTRY);
if (FAILED(hr))
{
cout << "OpenGPO failed";
return 1;
}
HKEY keyComputer;
hr = gpo->GetRegistryKey(GPO_SECTION_MACHINE, &keyComputer);
if (FAILED(hr))
{
cout << "GetRegistryKey failed";
return 1;
}
HKEY keyWsus;
if (RegOpenKeyEx(keyComputer, L"Software\\Policies\\Microsoft\\Windows\\WindowsUpdate", 0, KEY_SET_VALUE, &keyWsus) == ERROR_SUCCESS)
{
if (RegSetValueEx(keyWsus, L"WUServer", 0, REG_SZ, (BYTE *)wsusServer, (wcslen(wsusServer) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS)
{
cout << "RegSetValue failed";
return 1;
}
RegCloseKey(keyWsus);
}
else
{
cout << "RegOpenKey failed";
return 1;
}
RegCloseKey(keyComputer);
GUID registryId = REGISTRY_EXTENSION_GUID;
GUID AdminToolGuid = CLSID_PolicySnapinUser;
hr = gpo->Save(true, true, ®istryId, &AdminToolGuid);
if (FAILED(hr))
{
cout << "GpoSave failed";
return 1;
}
cout << "WSUS Server has been changed";
return 0;
}
C# .NET version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180using System;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
namespace wsusexample
{
class Program
{
// Must be in Single-Threaded-Apartment
[STAThread]
static void Main(string[] args)
{
var prg = new Program();
if (prg.setWsusServer("http://127.0.0.1"))
Console.WriteLine("WSUS address has been changed");
else
Console.WriteLine("WSUS address cannot be changed");
}
private bool setWsusServer(string wsusServer)
{
var gpo = new GroupPolicyClass();
IGroupPolicyObject igpo = (IGroupPolicyObject)gpo;
try
{
var hr = igpo.OpenLocalMachineGPO(GPO_OPEN_LOAD_REGISTRY);
if (hr != 0)
return false;
hr = igpo.GetRegistryKey(GPO_SECTION_MACHINE, out UIntPtr keyComputer);
if (hr != 0)
return false;
if (RegOpenKeyExW(keyComputer, @"Software\Policies\Microsoft\Windows\WindowsUpdate", 0, RegSAM.SetValue, out UIntPtr keyWsus) == 0)
{
IntPtr keyValue = Marshal.StringToBSTR(wsusServer);
int cbData = Encoding.Unicode.GetByteCount(wsusServer) + 2;
RegSetValueExW(keyWsus, "WUServer", 0, RegistryValueKind.String, keyValue, cbData);
Marshal.FreeBSTR(keyValue);
RegCloseKey(keyWsus);
}
RegCloseKey(keyComputer);
hr = igpo.Save(true, true, REGISTRY_EXTENSION_GUID, CLSID_PolicySnapinUser);
return (hr == 0);
}
catch
{
return false;
}
}
static readonly Guid REGISTRY_EXTENSION_GUID = new Guid("35378EAC-683F-11D2-A89A-00C04FBBCFA2");
static readonly Guid CLSID_PolicySnapinUser = new Guid( "0F6B957E-509E-11D1-A7CC-0000F87571E3");
public const uint GPO_OPEN_LOAD_REGISTRY = 0x00000001;
public const uint GPO_OPEN_READ_ONLY = 0x00000002;
public const uint GPO_SECTION_ROOT = 0;
public const uint GPO_SECTION_USER = 1;
public const uint GPO_SECTION_MACHINE = 2;
[ComImport, Guid("EA502722-A23D-11d1-A7D3-0000F87571E3")]
public class GroupPolicyClass
{
}
[ComImport, Guid("EA502723-A23D-11d1-A7D3-0000F87571E3"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IGroupPolicyObject
{
uint New(
[MarshalAs(UnmanagedType.LPWStr)] string domainName,
[MarshalAs(UnmanagedType.LPWStr)] string displayName,
uint flags);
uint OpenDSGPO(
[MarshalAs(UnmanagedType.LPWStr)] string path,
uint flags);
uint OpenLocalMachineGPO(
uint flags);
uint OpenRemoteMachineGPO(
[MarshalAs(UnmanagedType.LPWStr)] string computerName,
uint flags);
uint Save(
[MarshalAs(UnmanagedType.Bool)] bool machine,
[MarshalAs(UnmanagedType.Bool)] bool add,
[MarshalAs(UnmanagedType.LPStruct)] Guid extension,
[MarshalAs(UnmanagedType.LPStruct)] Guid app);
uint Delete();
uint GetName(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder name,
int maxLength);
uint GetDisplayName(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder name,
int maxLength);
uint SetDisplayName(
[MarshalAs(UnmanagedType.LPWStr)] string name);
uint GetPath(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder path,
int maxPath);
uint GetDSPath(
uint section,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder path,
int maxPath);
uint GetFileSysPath(
uint section,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder path,
int maxPath);
uint GetRegistryKey(
uint section,
out UIntPtr key);
uint GetOptions();
uint SetOptions(
uint options,
uint mask);
uint GetType(
out IntPtr gpoType);
uint GetMachineName(
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder name,
int maxLength);
uint GetPropertySheetPages(
out IntPtr pages);
}
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RegCloseKey(
UIntPtr hKey);
[DllImport("advapi32.dll", CharSet = CharSet.Auto)]
static extern int RegOpenKeyExW(
UIntPtr hKey,
[MarshalAs(UnmanagedType.LPWStr)] string subKey,
int ulOptions,
RegSAM samDesired,
out UIntPtr hkResult);
[DllImport("advapi32.dll", SetLastError = true)]
static extern int RegSetValueExW (
UIntPtr hKey,
[MarshalAs(UnmanagedType.LPWStr)] string lpValueName,
int Reserved,
RegistryValueKind dwType,
IntPtr lpData,
int cbData);
[Flags]
public enum RegSAM
{
QueryValue = 0x00000001,
SetValue = 0x00000002,
CreateSubKey = 0x00000004,
EnumerateSubKeys = 0x00000008,
Notify = 0x00000010,
CreateLink = 0x00000020,
WOW64_32Key = 0x00000200,
WOW64_64Key = 0x00000100,
WOW64_Res = 0x00000300,
Read = 0x00020019,
Write = 0x00020006,
Execute = 0x00020019,
AllAccess = 0x000F003F
}
}
}
Delphi version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90uses
Windows, ActiveX, Registry;
const
CLSID_GroupPolicyObject: TGUID = (D1:$ea502722; D2:$a23d; D3:$11d1; D4:($a7, $d3, $0, $0, $f8, $75, $71, $e3));
IID_IGroupPolicyObject: TGUID = (D1:$ea502723; D2:$a23d; D3:$11d1; D4:($a7, $d3, $0, $0, $f8, $75, $71, $e3));
REGISTRY_EXTENSION_GUID: TGUID = (D1:$35378EAC; D2:$683F; D3:$11D2; D4:($A8, $9A, $00, $C0, $4F, $BB, $CF, $A2));
CLSID_PolicySnapinUser: TGUID = (D1:$F6B957E; D2:$509E; D3:$11D1; D4:($A7, $CC, $00, $00, $F8, $75, $71, $E3));
GPO_OPEN_LOAD_REGISTRY = $00000001;
GPO_OPEN_READ_ONLY = $00000002;
GPO_SECTION_ROOT = 0;
GPO_SECTION_USER = 1;
GPO_SECTION_MACHINE = 2;
type
GROUP_POLICY_OBJECT_TYPE = (
GPOTypeLocal,
GPOTypeRemote,
GPOTypeDS);
IGroupPolicyObject = interface (IUnknown)
['{EA502723-A23D-11d1-A7D3-0000F87571E3}']
function New(pszDomainName, pszDisplayName: POleStr; dwFlags: DWORD): HRESULT; stdcall;
function OpenDSGPO(pszPath: POleStr; dwFlags: DWORD): HRESULT; stdcall;
function OpenLocalMachineGPO(dwFlags: DWORD): HRESULT; stdcall;
function OpenRemoteMachineGPO(pszComputerName: POleStr; dwFlags: DWORD): HRESULT; stdcall;
function Save(bMachine, bAdd: BOOL; const pGuidExtension, pGuid: TGUID): HRESULT; stdcall;
function Delete: HRESULT; stdcall;
function GetName(pszName: POleStr; cchMaxLength: Integer): HRESULT; stdcall;
function GetDisplayName(pszName: POleStr; cchMaxLength: Integer): HRESULT; stdcall;
function SetDisplayName(pszName: POleStr): HRESULT; stdcall;
function GetPath(pszPath: POleStr; cchMaxPath: Integer): HRESULT; stdcall;
function GetDSPath(dwSection: DWORD; pszPath: POleStr; cchMaxPath: Integer): HRESULT; stdcall;
function GetFileSysPath(dwSection: DWORD; pszPath: POleStr; cchMaxPath: Integer): HRESULT; stdcall;
function GetRegistryKey(dwSection: DWORD; var hKey: HKEY): HRESULT; stdcall;
function GetOptions(var dwOptions: DWORD): HRESULT; stdcall;
function SetOptions(dwOptions, dwMask: DWORD): HRESULT; stdcall;
function GetType(var gpoType: GROUP_POLICY_OBJECT_TYPE): HRESULT; stdcall;
function GetMachineName(pszName: POleStr; cchMaxLength: Integer): HRESULT; stdcall;
function GetPropertySheetPages(var hPages: Pointer; var uPageCount: UINT): HRESULT; stdcall;
end;
function SetWsusServer(WsusServer: string): Boolean;
const
WindowsUpdateRegKey = 'Software\Policies\Microsoft\Windows\WindowsUpdate';
var
hr: HRESULT;
LGPO: IGroupPolicyObject;
ComputerKey: HKEY;
Reg: TRegistry;
begin
Result := False;
hr := CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) then
Exit;
hr := CoCreateInstance(CLSID_GroupPolicyObject, nil, CLSCTX_INPROC_SERVER, IID_IGroupPolicyObject, LGPO);
if (FAILED(hr)) then
Exit;
try
hr := LGPO.OpenLocalMachineGPO(GPO_OPEN_LOAD_REGISTRY);
if (FAILED(hr)) then
Exit;
hr := LGPO.GetRegistryKey(GPO_SECTION_MACHINE, ComputerKey);
if (FAILED(hr)) then
Exit;
Reg := TRegistry.Create;
try
Reg.RootKey := ComputerKey;
if not Reg.OpenKey(WindowsUpdateRegKey, True) then
Exit;
Reg.WriteString('WUServer', WsusServer);
finally
Reg.Free;
end;
RegCloseKey(ComputerKey);
hr := LGPO.Save(True, True, REGISTRY_EXTENSION_GUID, CLSID_PolicySnapinUser);
Result := SUCCEEDED(hr);
finally
LGPO := nil;
CoUninitialize;
end;
end;
Leave a Reply