使用PowerShell,是否可以调用IUpdateInstaller4.提交没有类型库或COM互操作DLL?<



这个问题基本上是6年前的问题的后续:IUpdateInstaller4::Commit not found

正如Hans Passant在回答中所说的,在当时,不仅类型库完全没有这个接口,而且其他的(如IUpdateInstaller3)也没有。

快进到现在,Windows 10 20H2和更新的版本实际上有一个wuapi.dll,它在wuapi.dll中包含一个完整的类型库,MSDN文档已经更新。

这是iupdateinstaller4接口的链接(以及其他之前缺失的接口):https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdateinstaller4

也就是说,在旧版本的Windows 10上,如果你正在安装一个功能更新,IUpdateInstaller4。必须调用Commit才能正确安装特性更新。我的观察是,如果你没有做到这一点,你很可能最终得到一个Windows安装,要么回滚,要么让你的系统处于几乎不可用的状态(问我是怎么知道的)。

我正在寻找一种使用本地powershell的方法,以便能够在旧版本的Windows 10(如Windows 10 1909)上调用Commit方法。到目前为止,我唯一的成功是在较新版本的Windows 10(如21H2)上创建一个COM互操作DLL,并将该COM互操作DLL带回1909机器并利用它——,但我正在寻找一种方法来做到这一点,而不必将整个DLL运送到机器上。我怀疑有一种方法可以用Add-Type和一些c#代码来做到这一点,这是我理想的解决方案,这样整个Windows更新过程就可以由我编写的Powershell脚本来处理。

有很多,很多事情我已经尝试了,做的工作在win10 20H2+,但做NOT在Windows 10 1909上工作。一个可以接受的答案是在所有版本的Windows 10上都能工作。

我已经尝试了各种方法来声明IUpdateInstaller4(使用从我生成的COM互操作DLL中反编译的定义,并使用Add-Type引入类型),但没有成功。每次我尝试这样做,像这样:

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

…我得到一个异常,如:

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

我相当肯定熟悉COM内部工作原理的人(比如汉斯·帕桑特)可能会有一个快速的解决方案。


基于评论中的问题的附加信息/代码。下面是安装更新的脚本片段(基于创建一个基于几个脚本参数构造的$UpdateSearchString)

$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()

在Windows 10上,你应该可以这样做:

$CommitResult = $UpdateInstaller.Commit(0)

…但这不起作用,除非你的Windows 10版本在wuapi COM dll中有更新的类型库。

以下代码在更新了wuapi.dll的机器上运行,但不能在旧版本上运行(并且由于上面发布的异常而失败):

$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))

事实证明,Simon Mourier使我能够调用IUpdateInstaller4.Commit。

我不知道为什么即使有这个接口定义,你也不能调用Commit with:

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

. .但是它可以通过静态类/静态方法间接调用

这是一个例子,我在过去的几天里在旧版本的Windows 10以及服务器2016、2019、2022和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)

最新更新