Using PowerShell, is it possible to call IUpdateInstaller4.Commit without a type library or COM interop DLL?

402 views Asked by At

This question is essentially a follow-up to a 6+ years old question here: IUpdateInstaller4::Commit not found

As stated by Hans Passant in that answer, at the time, not only was the type library missing this interface entirely, but others (like IUpdateInstaller3) as well.

Fast forward to now, versions of Windows 10 20H2 and newer actually have a wuapi.dll that contains a complete type library within wuapi.dll, and the MSDN documentation has been updated.

Here is the link to theIUpdateInstaller4 interface (along with the rest of the previously missing interfaces): https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdateinstaller4

That said, on older versions of Windows 10, if you are installing a feature update, IUpdateInstaller4.Commit must be called in order for the feature update to properly install. My observation is that if you fail to do this, you are likely to end up with a Windows installation that either rolls back or leaves your system in a nearly unusable state (ask me how I know).

I am looking for a way using native powershell to be able to call the Commit method on older versions of Windows 10, such as Windows 10 1909. The only success I have had so far was to create a COM interop DLL on a newer version of Windows 10 (such as 21H2) and bring that COM interop DLL back over to the 1909 machine and utilize it--but I am looking for a way to do this without having to ship an entire DLL to a machine. I suspect there is a way to do this with Add-Type and some C# code, which is my ideal solution so that the entire Windows Update process can be handled by the Powershell script I've authored.

There are many, many things I have tried that DO work on Win 10 20H2+, but do NOT work on Windows 10 1909. An acceptable answer is something that works on ALL versions of Windows 10.

I have tried various ways of declaring IUpdateInstaller4 (using decompiled definitions from the COM interop DLL I generated, and bringing the type in with Add-Type) without success. Each time I try something along those lines, like this:

[wuapi.IUpdateInstaller4].GetType().InvokeMember('Commit',[Reflection.BindingFlags]::InvokeMethod, $null, $UpdateInstaller, @(0))

...I get an exception such as:

Exception calling "InvokeMember" with "5" argument(s): "Object does not match target type."
At line:85 char:2
 +     [wuapi.IUpdateInstaller4].GetType().InvokeMember('Commit',[Reflec …
 +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
 + FullyQualifiedErrorId : TargetException

I'm fairly certain someone who is familiar with the inner workings of COM (such as Hans Passant) would probably have a quick solution.


Additional information/code based on questions in the comments. Here is a snippet of the script that installs the updates (based on creating an $UpdateSearchString that is constructed based on several script parameters)

$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$Searcher.ServiceID = $UpdateServiceID
$Results = $Searcher.Search($UpdatesSearchString)
$Results.Updates | % {if($_.EulaAccepted -eq $false){$_.AcceptEula()}}
$UpdateList = $Results.Updates
$Updates = New-Object -ComObject Microsoft.Update.UpdateColl
$UpdateList | % {$null = $Updates.Add($_)}
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateInstaller = $UpdateSession.CreateUpdateInstaller()
while($UpdateInstaller.IsBusy) {Start-Sleep 5}
$UpdateInstaller.Updates = $Updates
$InstallResult = $UpdateInstaller.Install()

On Windows 10, you should be able to do this:

$CommitResult = $UpdateInstaller.Commit(0)

...but that does not work unless you are on a version of Windows 10 that has the updated type library in the wuapi COM dll.

The following code works on a machine with an updated wuapi.dll, but not older versions (and fails with the exception posted above):

$Source = @"
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace wuapi
{
  [TypeLibType(TypeLibTypeFlags.FHidden | TypeLibTypeFlags.FDual | TypeLibTypeFlags.FNonExtensible | TypeLibTypeFlags.FDispatchable)]
  [Guid("EF8208EA-2304-492D-9109-23813B0958E1")]
  [ComImport]
  public interface IUpdateInstaller4 : IUnknown
  {
    [DispId(1610874882)]
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    int Commit([In] uint dwFlags);
  }
  [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("00000000-0000-0000-C000-000000000046")]
    public interface IUnknown
    {
    }
  public static class UpdateCommitter
  {
    public static int Commit(IUnknown comObject, uint dwFlags)
    {
        var updateInstaller = comObject as IUpdateInstaller4;
        return updateInstaller.Commit(dwFlags);
    }
  }
}
"@
Add-Type -TypeDefinition $Source
$CommitResult=[wuapi.IUpdateInstaller4].InvokeMember('Commit',[Reflection.BindingFlags]::InvokeMethod, $null,  $UpdateInstaller, @(0))
1

There are 1 answers

0
Dimitri Rodis On BEST ANSWER

As it turns out, Simon Mourier put me on the right track to being able to call IUpdateInstaller4.Commit.

I'm not sure why even with this interface definition you cannot call Commit with:

$CommitResult=[wuapi.IUpdateInstaller4].InvokeMember('Commit',[Reflection.BindingFlags]::InvokeMethod, $null,  $UpdateInstaller, @(0))

..but it can be called indirectly via a static class/static method.

Here is an example that I have successfully tested and used the past couple of days on both older versions of Windows 10 as well as Server 2016, 2019, 2022, and Windows 11.

$Source = @"
using System;
using System.Runtime.InteropServices;
namespace wuapi
{
    [ComImport, Guid("ef8208ea-2304-492d-9109-23813b0958e1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IUpdateInstaller4
    {
        void _VtblGap0_29(); // skip 4 (IDispatch) + 25 IUpdateInstaller4 methods
        [PreserveSig]
        int Commit(uint dwFlags);
    }
    public static class UpdateCommitter
    {
        public static int Commit(object comObject, uint dwFlags)
        {
            var updateInstaller = (IUpdateInstaller4)comObject;
            return updateInstaller.Commit(dwFlags);
        }
    }
}
"@
Add-Type -TypeDefinition $Source
[int]$CommitResult = [wuapi.UpdateCommitter]::Commit($UpdateInstaller,0)