当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 通过路径获取CIMInstance

作者:精品下载站 日期:2024-12-14 08:03:52 浏览:12 分类:玩电脑

通过路径获取CIMInstance


我是 PowerShell Cmdlet 工作组的成员。我们一直在研究这个问题,这是一个有趣的问题。足够了,我花了一些时间研究它并编写一些测试代码。如果您使用 WMI/CIM,您可能对此感兴趣。就我个人而言,我从来不需要使用这种方法来处理 WMI/CIM,但显然其他 IT 专业人员确实需要这样做。

通过路径获取WMI

大多数人使用 WMI/CIM cmdlet 来获取大量信息,并使用过滤来缩小选择范围。但是,自从 VBScript 时代以来,您还可以通过引用 WMI 对象的路径来直接获取其路径。这是一个系统属性,您可以使用 Get-WMIObject 查看。

[玩转系统] 通过路径获取CIMInstance

格式并不难理解:Computername\namespace:classname.filter。将此属性视为 CIM 存储库中对象的地址。使用它可以让您直接转到具有性能优势的对象。使用传统语法并不难,如下所示:

Get-WmiObject -Class win32_service -filter "name='spooler'"

在我的笔记本电脑上这需要 241 毫秒。但是,我也可以使用路径使用 [wmi] 类型加速器。

[wmi]"root\cimv2:win32_service.Name='spooler'"

[玩转系统] 通过路径获取CIMInstance

这个表达式只花了 130 毫秒就检索到了我通过过滤得到的确切对象。如果您已经有了想要管理的 WMI 对象的路径,这似乎是一个明智的选择。

CIM 限制

我刚刚演示的所有内容都可以在 Windows PowerShell 中使用 Get-WMIObject 正常运行。但是,这种方法已被视为已弃用,现在我们使用 CIM cmdlet。这些命令查询相同的 CIM 存储库,只不过它们使用 WSMan 协议而不是 RPC 和 DCOM 来执行此操作。这是一件好事。不幸的是,Get-CimInstance 不返回 __Path 属性。

[玩转系统] 通过路径获取CIMInstance

自 PowerShell v3 以来就是这种情况。无论是错误还是设计决策都与我无关。但是,我有足够的信息来构造路径,我需要的只是可以使用 Get-CimClass 获取的类的关键属性。

[玩转系统] 通过路径获取CIMInstance

然后我就根据这个想法写了一个PowerShell函数。

Function Get-CimKeyProperty {
    [cmdletbinding()]
    [Outputtype("cimKeyProperty")]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Classname,
        [ValidateNotNullOrEmpty()]
        [string]$Namespace = "root\cimv2",
        [ValidateNotNullorEmpty()]
        [string]$Computername = $env:Computername
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $namespace$classname"
        Try {
            $cim = Get-CimClass @PSBoundParameters -ErrorAction stop
            [pscustomobject]@{
                PSTypename   = "cimKeyProperty"
                Namespace    = $Namespace
                Classname    = $cim.CimClassName
                Name         = $cim.cimclassproperties.where( { $_.qualifiers.name -contains 'key' }).Name
                Computername = $cim.CimSystemProperties.ServerName.toUpper()
            }
        }
        Catch {
            Throw $_
        }
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"

    } #end

}

该函数将丰富的对象写入管道。它还考虑了不同类型的路径,我不会在这里讨论。

[玩转系统] 通过路径获取CIMInstance

有了这些信息,我可以创建 __Path。

Function New-CimInstancePath {
    [cmdletbinding()]
    Param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]$Classname,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]$Name,
        [Parameter(ValueFromPipelineByPropertyName)]
        [string[]]$Value,
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [string]$Namespace = "root\cimv2",
        [Parameter(ValueFromPipelineByPropertyName)]
        [ValidateNotNullorEmpty()]
        [string]$Computername = $env:Computername
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $Namespace.$Class"
        if ($Name -is [array]) {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using a compound selector"
            $selectors = @()
            for ($i = 0; $i -lt $name.count; $i++) {
                $selectors += "{0}=""{1}""" -f $name[$i], $value[$i]
            }
            "\{0}\{1}:{2}.{3}" -f $Computername, $Namespace, $classname, ($selectors -join ",")
        }
        elseif ($Name -AND $Value) {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using $name property"
            "\{0}\{1}:{2}.{3}='{4}'" -f $Computername, $Namespace, $classname, $Name, $Value
        }
        else {
            #singleton
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using singleton path"
            "\{0}\{1}:{2}=@" -f $Computername, $Namespace, $classname
        }
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"

    } #end
}

我编写此函数是为了与 Get-CimKeyProperty 一起使用。

[玩转系统] 通过路径获取CIMInstance

最后一步是创建一个可以更新 CimInstance 的命令。

Function Add-CimInstancePath {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, ValueFromPipeline)]
        [ciminstance]$Instance
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $($Instance.CimSystemProperties.ClassName)"
        $system = $Instance.CimSystemProperties
        $get = @{
            Classname    = $system.ClassName
            Namespace    = $system.Namespace.replace("/", "\")
            Computername = $system.ServerName
        }
        $kp = Get-CimKeyProperty @get
        if ($kp.Name) {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Defining a path using property $($kp.name)"
            $newpath = $kp | New-CimInstancePath -value $instance.CimInstanceProperties[$($kp.name)].value
        }
        else {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Defining a singleton path"
            $newpath = $kp | New-CimInstancePath
        }

        if ($newpath) {
            $Instance.CimSystemProperties |
            Add-Member -MemberType NoteProperty -Name __Path -Value $newpath -Force
            #write the updated instance to the pipeline
            $instance
        }
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

此函数旨在获取 CImInstance 并将 __Path 属性添加到 CimSystemproperties。

[玩转系统] 通过路径获取CIMInstance

我可以将此值与 [WMI] 类型加速器一起使用。

[玩转系统] 通过路径获取CIMInstance

使用Winrm

然而,这并没有真正解决根本问题。在没有 Get-WmiObject 的世界中,我将如何处理此路径信息?目前,PowerShell 7 仍然支持 [wmi],尽管 Get-WmiObject 已消失。这对于本地查询来说很好。但是,如果您在远程服务器上获取实例,我很确定 [wmi] 正在使用旧协议,这是我们希望避免的。

但这是有趣的部分。我可以使用命令行工具 winrm.cmd 通过 WSMan 查询 WMI,就像 Get-CimInstance 所做的那样,我可以给它一个实例路径。

[玩转系统] 通过路径获取CIMInstance

问题是路径语法与我们使用 Get-WMIObject 获得的语法略有不同。换句话说,我需要将 \\THINKP1\root\cimv2:Win32_Service.Name=”Spooler” 转换为:wmi/root/cimv2/win32_service?name=spooler。计算机名是单独处理的。

我已经有了使用 Get-CimInstance 获取 __Path 值的代码。假设我要保存这个值以供以后使用,我编写了这个函数,该函数本质上将 WMI 路径转换为与 winrm 命令兼容的路径。

Function Get-CimInstancePath {
    #this function uses winrm.cmd. It may not properly process non-Cimv2 paths
    [cmdletbinding()]
    [alias("gcip")]
    [OutputType("CimInstance")]
    Param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [string]$InstancePath
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN] Starting $($myinvocation.mycommand)"
        [regex]$rx = '(("\(?<computername>\w+)\)?(?<namespace>(ROOT|root|Root)\.*(?=:)):)?(?<class>\w+(?=\.|\=))(\.(?<filter>.*))?'
    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $instancepath"
        if ($rx.IsMatch($InstancePath)) {
            $groups = $rx.match($InstancePath).groups
            $computername = $groups['computername'].Value
            $filter = $groups['filter'].Value.replace(",", "+")
            $class = $groups['class'].Value

            $namespace = $groups['namespace'].value

            $ns = "wmi/$($namespace.replace('\','/'))"

            [string]$get = "$ns/$class"
            if ($filter) {
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Appending filter $filter"
                $get += "?$filter"
            }
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting $get"
            #Write-Host "winrm get $get" -ForegroundColor cyan
            #winrm get $get.trim() >d:\temp\r.txt
            #$global:g = $get
            if ($computername) {
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Querying remote computer $computername"
                <#
                        Normally I would never use Invoke-Expression, but PowerShell is doing
                        something odd with multi-selector paths like Win32_Bios and failing.
                        This seems the best option.
                    #>
                [xml]$raw = Invoke-Expression "winrm get $get -r:$Computername -f:xml 2>$env:temp\e.txt"
            }
            else {
                [xml]$raw = Invoke-Expression "winrm get $get -f:xml 2>$env:temp\e.txt"
            }

            if ($raw) {

                #insert the corresponding CIM type name
                $cimtype = "Microsoft.Management.Infrastructure.CimInstance#$($namespace.replace("\","/"))/$class"
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Inserting typename $cimtype"
                $result = $raw.$class
                $result.psobject.typenames.insert(0, $cimtype)
                #write the result to the pipeline
                $result
            }
            elseif (Test-Path $env:temp\e.txt) {
                $m = '(?<=f:Message\>)(.|\n)*(?=\n\<\/f:Message)'
                #'Message(.*\n.*)*'
                $msg = [System.Text.RegularExpressions.Regex]::Match((Get-Content $env:temp\e.txt -Raw), $m).value.trim()
                Write-Warning "Failed to get CIM Instance. `n$msg"
            }

            if (Test-Path $env:temp\e.txt) {
                # Remove-Item $env:temp\e.txt
            }
        }
        else {
            Write-Warning "Failed to parse instance $instancepath"
        }
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

此函数不仅使用 winrm 获取路径并获取信息,还创建一个对象并插入相应的 CIM 属性名称,这让 PowerShell 格式化输出,就像使用 Get-CimInstance 一样。

$p = (get-ciminstance win32_service -filter "name='bits'" | Add-CimInstancePath).cimsystemproperties.__Path
Get-CimInstancePath -InstancePath $p

[玩转系统] 通过路径获取CIMInstance

现在我有一个使用 CIM 并通过实例路径检索 WMI 对象的命令。无法保证我的代码适用于所有 WMI 类,但我希望它适用于 Root\Cimv2 中的默认 win32 类。

下一步

我可能会将这些函数以模块的形式发布到 PowerShell Gallery。至于PowerShell 7和悬而未决的问题,我不确定微软应该采取什么方法。他们可以向 Get-CimInstance 添加一个参数,以通过实例的路径获取实例。尽管他们还需要修复丢失的系统路径属性。另一种选择是引入 [cim] 类型加速器,其工作方式与 [wmi] 相同。假设的 [cim] 的工作方式与我的 Get-CimInstancePath 相同。

与此同时,我希望你能尝试一下这些事情。如果您的工作利用通过路径获取 WMI 对象,我很想听听。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯