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

[玩转系统] 解决 PowerShell 转换挑战

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

解决 PowerShell 转换挑战


今天,让我们看看我是如何应对今年第一个 Iron Scripter PowerShell 挑战的。挑战的目标是将对象转换或翻译为 PowerShell 类定义。如果您是这些挑战的新手,那么实现目标的过程比最终结果更有价值。这些挑战旨在成为学习练习。

从单个对象开始

为了定义一个新的对象类,我需要一个对象作为我的模板。

$obj=Get-CimInstancewin32_operatingsystem-ComputerNamelocalhost

在此特定示例中,我还可以使用 Get-CimClass 列出属性名称。但由于我希望能够从任何类型的对象定义一个类,所以我将忽略这个替代方案。

相反,我将依赖隐藏的 PowerShell 功能。 PowerShell 易于使用的原因之一是它抽象或隐藏了许多 .NET 开发人员的内容。这很棒,因为这意味着您不必成为开发人员即可使用 PowerShell。然而,随着您获得经验,您可以剥离其中一些抽象概念。这就是我要做的。我将获得一个通用的psobject属性。

[玩转系统] 解决 PowerShell 转换挑战

这是对象的更通用的表示。让我们更深入地探讨一下。

[玩转系统] 解决 PowerShell 转换挑战

我可以看到属性名称及其类型,这将有助于定义我的类。 PowerShell 类定义如下所示:

class myClass {
[typename]$PropertyName
...
}

我可以使用 -f 运算符定义属性线。

$prop=$obj.PSobject.properties|Select-Object-First1
"[{0}]`"-f$prop.TypeNameOfValue,$prop.name

{0} 和 {1} 条目是占位符,它们是使用 -f 运算符右侧的值填充的。我还转义了 $,所以它是按字面意思处理的。创建的字符串为 [System.Boolean]$PSShowComputerName

考虑到这一点,我可以生成该对象的完整类版本。

$obj.psobject.properties|ForEach-Object-Begin{
$class=@"
classmyOS{
#properties


"@
}-Process{$class+="[{0}]``n"-f$_.TypeNameOfValue,$_.name}-End{$class+="}"}

这就是我最终得到的结果。

class myOS {
 properties
 [System.Boolean]$PSShowComputerName
 [string]$Caption
 [string]$Description
 [CimInstance#DateTime]$InstallDate
 [string]$Name
 [string]$Status
 [string]$CreationClassName
 [string]$CSCreationClassName
 [string]$CSName
 [int16]$CurrentTimeZone
 [bool]$Distributed
 [uint64]$FreePhysicalMemory
 [uint64]$FreeSpaceInPagingFiles
 [uint64]$FreeVirtualMemory
 [CimInstance#DateTime]$LastBootUpTime
 [CimInstance#DateTime]$LocalDateTime
 [uint32]$MaxNumberOfProcesses
 [uint64]$MaxProcessMemorySize
 [uint32]$NumberOfLicensedUsers
 [uint32]$NumberOfProcesses
 [uint32]$NumberOfUsers
 [uint16]$OSType
 [string]$OtherTypeDescription
 [uint64]$SizeStoredInPagingFiles
 [uint64]$TotalSwapSpaceSize
 [uint64]$TotalVirtualMemorySize
 [uint64]$TotalVisibleMemorySize
 [string]$Version
 [string]$BootDevice
 [string]$BuildNumber
 [string]$BuildType
 [string]$CodeSet
 [string]$CountryCode
 [string]$CSDVersion
 [bool]$DataExecutionPrevention_32BitApplications
 [bool]$DataExecutionPrevention_Available
 [bool]$DataExecutionPrevention_Drivers
 [byte]$DataExecutionPrevention_SupportPolicy
 [bool]$Debug
 [uint32]$EncryptionLevel
 [byte]$ForegroundApplicationBoost
 [uint32]$LargeSystemCache
 [string]$Locale
 [string]$Manufacturer
 [string[]]$MUILanguages
 [uint32]$OperatingSystemSKU
 [string]$Organization
 [string]$OSArchitecture
 [uint32]$OSLanguage
 [uint32]$OSProductSuite
 [bool]$PAEEnabled
 [string]$PlusProductID
 [string]$PlusVersionNumber
 [bool]$PortableOperatingSystem
 [bool]$Primary
 [uint32]$ProductType
 [string]$RegisteredUser
 [string]$SerialNumber
 [uint16]$ServicePackMajorVersion
 [uint16]$ServicePackMinorVersion
 [uint32]$SuiteMask
 [string]$SystemDevice
 [string]$SystemDirectory
 [string]$SystemDrive
 [string]$WindowsDirectory
 [string]$PSComputerName
 [Microsoft.Management.Infrastructure.CimClass]$CimClass
 [Microsoft.Management.Infrastructure.Generic.CimKeyedCollection`1[[Microsoft.Management.Infrastructure.CimProperty, Microsoft.Management.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]]$CimInstanceProperties
 [Microsoft.Management.Infrastructure.CimSystemProperties]$CimSystemProperties
 }

我可能不需要所有这些属性,我稍后会处理它。但首先让我尝试使用不同类型的物体并完善我的技术。

$properties = "Name","Displayname","MachineName","StartType"
(Get-Service bits).psobject.properties |
Where-Object {$properties -contains $_.name} |
ForEach-Object -Begin {
$class = @"
class mySvc {
#properties


"@
} -Process { $class += "[{0}]``n" -f $_.TypeNameOfValue, $_.name } -End { $class += "}" }

我的结果是:

class mySvc {
 properties
 [System.String]$Name
 [System.String]$DisplayName
 [System.String]$MachineName
 [System.ServiceProcess.ServiceStartMode]$StartType
 }

转换对象到类

现在我已经从提示中获得了工作代码,我可以将其封装在一个函数中并添加功能。

Function Convert-ObjectToClass {
    [cmdletbinding()]
    [outputType([String])]
    Param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [object]$InputObject,
        [Parameter(Mandatory, HelpMessage = "Enter the name of your new class")]
        [string]$Name,
        [string[]]$Properties,
        [string[]]$Exclude
    )
    Begin {
        Write-Verbose "Starting $($myinvocation.MyCommand)"
    } #begin

    Process {
        Write-Verbose "Converting existing type $($InputObject.getType().Fullname)"
        #create a list to hold properties
        $prop = [system.collections.generic.list[object]]::new()

        #define the class here-string
        $myclass = @"

# this class is derived from $($InputObject.getType().Fullname)
class $Name {
#properties

"@

        #get the required properties
        if ($Properties) {
            $InputObject.psobject.properties | Where-Object { $Properties -contains $_.name } |
            Select-Object -Property Name, TypeNameOfValue |
            ForEach-Object {
                Write-Verbose "Adding $($_.name)"
                $prop.Add($_)
            } #foreaach
        } #if Properties
        else {
            Write-Verbose "Adding all properties"
            $InputObject.psobject.properties | Select-Object -Property Name, TypeNameOfValue |
            ForEach-Object {
                Write-Verbose "Adding $($_.name)"
                $prop.Add($_)
            } #foreach
        } #else all

        if ($Exclude) {
            foreach ($item in $Exclude) {
                Write-Verbose "Excluding $item"
                #remove properties that are tagged as excluded from the list
                [void]$prop.remove($($prop.where({ $_.name -like $item })))
            }
        }
        Write-Verbose "Processing $($prop.count) properties"
        foreach ($item in $prop) {
            #add the property definition name to the class
            #e.g. [string]$Name
            $myclass += "[{0}]`${1}`n" -f $item.TypeNameOfValue, $item.name
        }

        #add placeholder content to the class definition
        $myclass += @"

#Methods can be inserted here
    <#
    [returntype] MethodName(<parameters>) {
        code
        return value
    }
    #>

#constructor placeholder
$Name() {
    #insert code here
}

} #close class definition
"@

        #if running VS Code or the PowerShell ISE, copy the class to the clipboard
        #this code could be modified to insert the class into the current document
        if ($host.name -match "ISE|Visual Studio Code" ) {
            $myClass | Set-Clipboard
            $myClass
            Write-Host "The class definition has been copied to the Windows clipboard." -ForegroundColor green
        }
        else {
            $myClass
        }
    } #process
    End {
        Write-Verbose "Ending $($myinvocation.MyCommand)"
    } #end
}

函数名称有争议。我使用的名称实际上是函数将完成的任务的字面表示。但“ObjectToClass”是一个奇怪的名词。更好的名称可能是 ConvertTo-PSClass。还可以使用不同的动词进行论证,例如OutExport。这些都是不容忽视的设计考虑因素。

我的函数包括 PowerShell 代码来检测它是否在 PowerShell ISE 或 VS Code 中运行,如果是,则将类复制到剪贴板。我有点犹豫是否将其包含在函数中,因为这是与定义类不同的任务,并且 PowerShell 函数应该只做一件事。但是,这段代码没有添加任何管道输出,从我的角度来看,我正在运行该函数来创建一个可以在脚本项目中使用的类定义。

我可以这样使用它:

Get-Ciminstance win32_Operatingsystem | Convert-ObjectToClass -Properties Caption,CSName,Version,InstallDate -Name myOS

类定义远非完美,我希望必须对其进行改进。

this class is derived from Microsoft.Management.Infrastructure.CimInstance
 class myOS {
 properties
 [string]$Caption
 [CimInstance#DateTime]$InstallDate
 [string]$CSName
 [string]$Version
 Methods can be inserted here
 <# [returntype] MethodName(<parameters>) {     code     return value } #>
 constructor placeholder
 myOS() {
     #insert code here
 }
 } #close class definition

例如,InstallDate 需要是 [DateTime] 对象。我还知道我需要重命名一些属性,例如将 $CSName 重命名为 $Computername。但我的职能让我开始了。

既然已经开始了,我不妨给大家介绍一下整个过程。这是我修改后的类定义。

class myOS {
    #properties
    [string]$Name
    [DateTime]$InstallDate
    [string]$Computername = $env:COMPUTERNAME
    [string]$Version
    [datetime]$AuditDate = (Get-Date)

    #Methods can be inserted here

    hidden [timespan] GetInstallAge() {
        $time = New-TimeSpan -Start $this.InstallDate -End (Get-Date)
        return $time
    }

    #constructor placeholder
    myOS([string]$Computername) {
        $os = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $Computername
        $this.name = $os.caption
        $this.version = $os.Version
        $this.installDate = $os.InstallDate
        $this.Computername = $Computername
    }

} #close class definition

因为我正在定义自定义对象,所以我还可以定义类型和格式扩展,尽管我现在跳过后者。

Update-TypeData -TypeName myOS -MemberType ScriptProperty -MemberName InstallAge -Value { $this.GetInstallAge() } -Force

我最不需要的就是为用户提供一种使用该类的简单方法。我不想强迫用户必须使用原始类定义。相反,创建您自己的辅助函数。

Function Get-OS {
    [cmdletbinding()]
    [Outputtype("myOS")]
    Param([string[]]$Computername = $env:COMPUTERNAME)

    foreach ($computer in $computername) {
        New-Object -TypeName myOS -ArgumentList $computer
    }
}

[玩转系统] 解决 PowerShell 转换挑战

我不得不承认,我可能真的会用到这个功能。这绝对不是初学者级别的 PowerShell,所以如果您有疑问,请不要害羞并在评论中提问。

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

取消回复欢迎 发表评论:

关灯