views:

112

answers:

2

I am writing a script in Powershell to get the update information from each computer and correlate the information with another System which identifies updates by CVE ID. I have discovered that there is a "CVEIDs" property for an update in WSUS, which is documented in MSDN, but I have no idea how to access the property. Retrieving the CVE ID from WSUS is the key to this script, so I am hoping someone out there can help!

Here is the property that I am having difficulty accessing:

IUpdate2::CveIDs Property - http://msdn.microsoft.com/en-us/library/aa386102(VS.85).aspx

According to this, the IUnknown::QueryInterface method is needed to interface IUpdate2 -  "http://msdn.microsoft.com/en-us/library/ee917057(PROT.10).aspx"

"An IUpdate instance can be retrieved by calling the IUpdateCollection::Item (opnum 8) (section 3.22.4.1) method. 

The client can use the IUnknown::QueryInterface method to then obtain an IUpdate2, IUpdate3, IUpdate4, or IUpdate5 interface. Additionally, if the update is a driver, the client can use the IUnknown::QueryInterface method to obtain an IWindowsDriverUpdate, IWindowsDriverUpdate2, IWindowsDriverUpdate3, IWindowsDriverUpdate4, or IWindowsDriverUpdate5 interface. "

Here is a skeleton of my code:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration") | Out-Null 

if (!$wsus)  { 

Returns an object that implements IUpdateServer 

$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer($server, $false, $port)  } 

$computerScope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope  $updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope  $updateScope.UpdateSources = [Microsoft.UpdateServices.Administration.UpdateSources]::MicrosoftUpdate  $wsusMachines = $wsus.GetComputerTargets($computerScope) 

foreach machine in QSUS, write the full domain name

$wsusMachines | ForEach-Object {  Write-host $.FullDomainName  $updates = $.GetUpdateInstallationInfoPerUpdate($updateScope) 

foreach update for each machine, write the update title, installation state and securitybulletin

$updates | ForEach-Object {  $update = $wsus.GetUpdate($.UpdateId) # Returns an object that implements Microsoft.UpdateServices.Administration.IUpdate $updateTitle = $update.Title | Write-Host $updateInstallationState = $.UpdateInstallationState | Write-Host $updateSecurityBulletin = $update.SecurityBulletins | Write-Host  $updateCveIds = $update.CveIDs # ERROR: Property 'CveIDs' belongs to IUpdate2, not IUpdate  }  } 

A: 

I'm not familiar with this API but in cases of COM query interface, try a cast in PowerShell where you are retrieving the object implementing IUpdate:

[Microsoft.UpdateServices.Administration.IUpdate2]$wsus.GetUpdate($.UpdateId)
Keith Hill
Thanks for the suggestion Keith. The IUpdate2 Interface is not accessible via Microsoft.UpdateServices.Administration. Instead, as outlined in MSDN, it must be obtained via the IUnknown::QueryInterface method. I have tried a few variations, such as this but haven't had any success: [IUnknown].QueryInterface("IUpdate2, $update") [IUnknown].GetMethod("QueryInterface").Invoke("IUpdate2",$update) The IUknown and IUpdate2 Interfaces are there in the registry on the WSUS server in HKEY_CLASSES_ROOT\Interface\.Related: http://stackoverflow.com/questions/1637999/com-interface-wrappers-in-powershell
thebitsandthebytes
yeah, you can't actually cast to interfaces in powershell. the members are exposed in a flat manner. this means it's nearly impossible to access members that are explicitly implemented; reflection is the only way.
x0n
That's a bummer. We were doing this sort of QI'ing via a cast back in the late 90's in an interpreted GPL I worked on i.e. it isn't rocket science.
Keith Hill
The only reliable way to get this working is to use inline C# with add-type cmdlet.
x0n
A: 

You could try using my get-interface script, it might work but I'm guessing it won't as powershell munges and special-cases COM objects. Your mileage may vary:

function Get-Interface {

#.Synopsis
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#.Description
#   Allows PowerShell to call specific interface implementations on any .NET object. 
#
#   As of v2.0, PowerShell cannot cast .NET instances to a particular interface. This makes it
#   impossible (shy of reflection) to call methods on explicitly implemented interfaces.   
#.Parameter Object
#   An instance of a .NET class from which you want to get a reference to a particular interface it defines.
#.Parameter InterfaceType
#   An interface type, e.g. [idisposable], implemented by the target object.
#.Example
#   // a class with explicitly implemented interface   
#   public class MyObj : IDisposable {
#      void IDisposable.Dispose()
#   }
#   
#   ps> $o = new-object MyObj
#   ps> $i = get-interface $o ([idisposable])
#   ps> $i.Dispose()      
#.ReturnValue
#   A PSCustomObject with ScriptMethods and ScriptProperties representing methods and properties on the target interface.
#.Notes
# AUTHOR:    Oisin Grehan http://www.nivot.org/
# LASTEDIT:  2009-03-28 18:37:23
# REVISION:  0.2

    [CmdletBinding()]
    param(
        [parameter(valuefrompipeline=$true, position=0)]
        [ValidateNotNull()]
        $Object,

        [parameter(position=1)]            
        [ValidateScript( { $_.IsInterface } )]
        [type]$InterfaceType
    )

    $script:t  = $Object.GetType()

    try {

        $script:m  = $t.GetInterfaceMap($InterfaceType)

    } catch [argumentexception] {

        throw "Interface $($InterfaceType.Name) not found on ${t}!"
    }

    $script:im = $m.InterfaceMethods
    $script:tm = $m.TargetMethods

    # TODO: use param blocks in functions instead of $args
    #       so method signatures are visible via get-member

    $body = {
         param($o, $i) 

         $script:t  = $o.GetType()
         $script:m  = $t.GetInterfaceMap($i)
         $script:im = $m.InterfaceMethods
         $script:tm = $m.TargetMethods

         for ($ix = 0; $ix -lt $im.Count; $ix++) {

            $mb = $im[$ix]

            # for the function body, we close over $ix to capture the index
            # so even on the next iteration of this loop, the $ix value will
            # be frozen within the function's scriptblock body
            set-item -path function:script:$($mb.Name) -value {

                # call corresponding target method
                $tm[$ix].Invoke($o, $args)

            }.GetNewClosure() -verbose -force

            if (!$mb.IsSpecialName) {
                # only export the function if it is not a getter or setter.
                Export-ModuleMember $mb.Name -verbose
            }
         }
    }

    write-verbose $body.tostring()    

    # create dynamic module
    $module = new-module -ScriptBlock $body -Args $Object, $InterfaceType -Verbose

    # generate method proxies - all exported members become scriptmethods
    # however, we are careful not to export getters and setters.
    $custom = $module.AsCustomObject()

    # add property proxies - need to use scriptproperties here.
    # modules cannot expose true properties, only variables and 
    # we cannot intercept variables get/set.

    $InterfaceType.GetProperties() | % {

        $propName = $_.Name
        $getter   = $null
        $setter   = $null

        if ($_.CanRead) {

            # where is the getter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetGetMethod())

            # bind the getter scriptblock to our module's scope
            # and generate script to call target method
            #
            # NOTE: we cannot use a closure here because sessionstate
            #       is rebound to the module's, and $ix would be lost
            $getter = $module.NewBoundScriptBlock(
                [scriptblock]::create("`$tm[{0}].Invoke(`$o, @())" -f $ix))
        }

        if ($_.CanWrite) {

            # where is the setter methodinfo on the interface map?
            $ix = [array]::indexof($im, $_.GetSetMethod())

            # bind the setter scriptblock to our module's scope
            # and generate script to call target method
            #
            # NOTE: we cannot use a closure here because sessionstate
            #       is rebound to the module's, and $ix would be lost
            $setter = $module.NewBoundScriptBlock(
                [scriptblock]::create(
                    "param(`$value); `$tm[{0}].Invoke(`$o, `$value)" -f $ix))
        }

        # add our property to the pscustomobject
        $prop = new-object management.automation.psscriptproperty $propName, $getter, $setter
        $custom.psobject.properties.add($prop)
    }

    # insert the interface name at the head of the typename chain (for get-member info)
    $custom.psobject.TypeNames.Insert(0, $InterfaceType.FullName)

    # dump our pscustomobject to pipeline
    $custom
}
x0n
thebitsandthebytes