RSS icon Home icon
  • Configure WSUS/GPO programmatically

    Posted on June 22nd, 2019 admin No comments

    We 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:

    1. Open GPO for the computer and load registry information: IGroupPolicyObject::OpenLocalMachineGPO()
    2. Obtain the root registry handle for the computer section: IGroupPolicyObject::GetRegistryKey()
    3. Open the registry key, change value then close: RegOpenKeyEx(), RegSetValueEx(), RegCloseKey()
    4. 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: WUServer

    The original article about modification of Group Policy Objects can be found here:

    Working with Group Policy Objects Programmatically – Determining registry values to Enable/Disable/Set a specific policy

    Working with Group Policy Objects Programmatically – simple C++ example illustrating how to modify a registry based policy

    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, &registryId, &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
    180
    using 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
    90
    uses
      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

    Your email address will not be published. Required fields are marked *