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

[玩转系统] 应对 WSMan PowerShell 挑战

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

应对 WSMan PowerShell 挑战


今天,我想分享一下我最近的 Iron Scripter 挑战的解决方案。我认为这是一项令人着迷的任务,并且具有实际成果。我鼓励你尝试一下挑战,然后回来看看我是如何应对的。

该挑战不仅是对您的 PowerShell 脚本编写技能的良好测试,也是对您对 PowerShell 远程处理的理解程度的测试。从长远来看,我毫不怀疑 PowerShell 远程处理将意味着 SSH。但这不会很快发生。使用 WSMan 的传统 PowerShell 远程处理仍然是大多数 IT 专业人员管理远程服务器的方式。这意味着您应该理解它并能够管理它。

脚本挑战是创建一个工具,该工具可以查询远程服务器的 WSMan 连接,并将具有特定信息的自定义对象写入管道。我是这样处理的。

了解您的流程

首先要知道的是,当建立 PowerShell 远程连接时,它会在 wsmprovhost 进程中运行。查询这个过程很简单。

[玩转系统] 应对 WSMan PowerShell 挑战

[玩转系统] 应对 WSMan PowerShell 挑战

流程对象可以提供挑战所寻找的许多信息。尽管如此,要求之一是获取关联的子进程。我发现使用 Get-CimInstance 对于此任务很有用。

[玩转系统] 应对 WSMan PowerShell 挑战

获取 WSManInstance

挑战的其他部分需要付出更多努力。当建立 PowerShell 远程连接时,就会有一个相应的 WSMan 实例。您可能知道,当您通过 PowerShell 远程处理进行连接时,您正在连接到会话端点。 Get-PSSessionConfiguration cmdlet 显示这些端点。这些端点几乎总是在远程计算机上启动 PowerShell 会话。用 WSMan 的话来说,它在 shell 中运行。

PowerShell 有一个名为 Get-WSManInstance 的 cmdlet,可让您枚举这些连接。

[玩转系统] 应对 WSMan PowerShell 挑战

幸运的是,有一个 XML 方法可以转换这些值。

[玩转系统] 应对 WSMan PowerShell 挑战

获取 PSRemoteSessionUser

那些棘手的地方。使用 Resolve-DNSName 可以轻松完成从 IP 地址获取源主机名等任务。考虑到所有这些,我编写了一个名为 Get-PSRemoteSessionUser 的 PowerShell 函数。

Function Get-PSRemoteSessionUser {
    [cmdletbinding(DefaultParameterSetName = "ComputerName")]
    Param(
        [Parameter(ParameterSetName = 'Session', Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.PSSession[]]$Session,

        [Parameter(ParameterSetName = 'ComputerName', Mandatory, Position = 0)]
        [Alias('Cn')]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName,

        [Parameter(ParameterSetName = 'ComputerName', ValueFromPipelineByPropertyName)]
        [pscredential]$Credential,

        [Parameter(ParameterSetName = 'ComputerName')]
        [System.Management.Automation.Runspaces.AuthenticationMechanism]$Authentication,

        [Parameter(ParameterSetName = 'ComputerName')]
        [Parameter(ParameterSetName = 'Session')]
        [int]$ThrottleLimit
    )

    Begin {
        Write-Verbose "[BEGIN  ] Starting: $($MyInvocation.Mycommand)"
        #define the scriptblock to run remotely

        $run = {
            $VerbosePreference = $using:VerbosePreference
            Write-Verbose "[$([environment]::machinename)] Querying for local WSMan sessions"
            $process = 'wsmprovhost'

            #these helper functions will be used to get and format connection data
            Function _enumWsMan {
                [cmdletbinding()]
                Param()

                Get-WSManInstance -ResourceURI Shell -Enumerate |
                    Select-Object -Property Name, State, ShellID, Owner, ClientIP, ProcessID,
                    @{Name = "Memory"; Expression = { _parseMemoryString $_.memoryUsed } },
                    @{Name = "ShellRunTime"; Expression = { [System.Xml.XmlConvert]::ToTimeSpan($_.ShellRunTime) } },
                    @{Name = "ShellInactivity"; Expression = { [System.Xml.XmlConvert]::ToTimeSpan($_.ShellInactivity) } },
                    @{Name = "MaxIdleTimeout"; Expression = { [System.Xml.XmlConvert]::ToTimeSpan($_.MaxIdleTimeout) } },
                    @{Name = "SessionConfiguration"; Expression = { Split-Path -path $_.resourceuri -leaf } }

            }
            Function _parseMemoryString {
                #convert values like 11MB to 11534336
                [cmdletbinding()]
                Param([string]$StringValue)

                switch -Regex ($StringValue ) {
                    "\d+KB" {
                        $val = 1KB
                    }
                    "\d+MB" {
                        $val = 1MB
                    }
                    "\d+GB" {
                        $val = 1GB
                    }

                } #switch
                if ($val) {
                    [int]$i = ([regex]"\d+").Match($StringValue).value
                    $i * $val
                }
                else {
                    Write-Warning "Failed to parse $StringValue"
                    $stringValue
                }

            } #close function

            try {
                Write-Verbose "[$([environment]::machinename)] Getting $process process excluding id $PID"
                $p = (Get-Process -Name $process -IncludeUserName -erroraction stop).where({ $_.id -ne $pid })
            }
            Catch [Microsoft.PowerShell.Commands.ProcessCommandException] {
                Write-Warning "Could not find process $process on this computer."
            }
            Catch {
                Throw $_
            }

            if ($p) {

                foreach ($item in $p) {

                    if ($item.username) {
                        Write-Verbose "[$([environment]::machinename)] Getting the SID for $($item.username)"
                        $SID = [System.Security.Principal.NTAccount]::new("$($item.username)").Translate([System.Security.Principal.SecurityIdentifier]).value
                    }
                    else {
                        $SID = $null
                    }
                    #call a private function to enumerate WSMan connection associated with this process ID
                    Write-Verbose "[$([environment]::machinename)] Enumerating WSMan connections"
                    $connData = $(_enumWsMan).where({ $_.processid -eq $item.ID })

                    #get child process IDs
                    Write-Verbose "[$([environment]::machinename)] Getting child processes for id $($item.id)"
                    $childProcs = (Get-CimInstance -ClassName win32_process -filter "ParentProcessId = $($item.id)" -Property ProcessID).ProcessID

                    #resolve the hostname
                    #temporarily disable Verbose to eliminate verbose messages from loading the DNSClient module
                    $VerbosePreference = "SilentlyContinue"
                    Import-Module DNSClient
                    $VerbosePreference = $using:VerbosePreference
                    Try {
                        Write-Verbose "[$([environment]::machinename)] Resolving the hostname for $($conndata.ClientIP)"
                        $rHost = (Resolve-DnsName -Name $connData.ClientIP -ErrorAction Stop).NameHost
                    }
                    Catch {
                        Write-Verbose "[$([environment]::machinename)] Failed to resolve a hostname for $($connData.ClientIP)."
                        $rHost = $connData.clientIP
                    }
                     Write-Verbose "[$([environment]::machinename)] Returning connection data"
                    #Send data back to the host to construct a custom object
                    @{
                        rHost        = $rHost
                        Item         = $item
                        SID          = $SID
                        Computername = [environment]::MachineName
                        ChildProcs   = $childProcs
                        ConnData     = $connData
                    }

                } #foreach item
            }
            else {
                Write-Verbose "[$([environment]::machinename)] No running $process process(e$as) found"
            }
        } #close scriptblock

        $PSBoundParameters.add("Scriptblock", $Run)
        $PSBoundParameters.Add("HideComputername", $True)
    } #begin

    Process {
        Write-Verbose "[PROCESS] Getting remote connection data"
        $data = Invoke-Command @PSBoundParameters
        foreach ($result in $data) {
            Write-Verbose "[PROCESS] Processing data for $($result.computername)"
            [pscustomobject]@{
                PSTypename           = "PSRemoteSessionUser"
                Computername         = $result.Computername
                DateUTC              = (Get-Date).ToUniversalTime()
                StartTimeUTC         = $result.item.StartTime.ToUniversalTime()
                ProcessID            = $result.item.id
                ChildProcesses       = $result.childProcs
                Username             = $result.item.Username
                SID                  = $result.sid
                State                = $result.connData.State
                RemoteHost           = $result.rHost
                RemoteIPAddress      = $result.connData.ClientIP
                ShellRunTime         = $result.conndata.ShellRunTime
                SessionConfiguration = $result.connData.SessionConfiguration
                Memory               = $result.connData.Memory
            }
        } #foreach result
    } #process

    End {
        Write-Verbose "[END    ] Ending: $($MyInvocation.Mycommand)"
    } #end

} #close Get-RemoteSessionUser

该功能旨在让我通过名称或通过现有会话查询远程计算机。如果我使用现有会话,它将从结果中过滤掉。大多数核心查询都是远程发生的。 “原始”数据从 Invoke-Command 返回到本地主机,并转换为自定义对象。

我的函数使用详细输出,甚至将我的详细首选项传递给远程计算机。

[玩转系统] 应对 WSMan PowerShell 挑战

[玩转系统] 应对 WSMan PowerShell 挑战

自定义格式

但还有一件事。挑战的最后一部分是创建自定义格式文件。我得到的结果很好,但也许我通常只需要查看格式化的属性子集。使用 PSScriptTools 模块中的 New-PSFormatXML,我创建了一个 format.ps1xml。在 VS Code 中进行一些调整后,我可以将其加载到我的会话中。

Update-FormatData psremotesessionuser.format.ps1xml

现在就可以得到格式良好的结果。

[玩转系统] 应对 WSMan PowerShell 挑战

这是格式文件。

<?xml version="1.0" encoding="UTF-8"?>
<!--
format type data generated 09/15/2020 08:54:32 by BOVINE320\Jeff
using New-PSFormatXML from the PSScriptTools module.

Install-Module PSScriptTools
-->
<Configuration>
  <ViewDefinitions>
    <View>
      <!--Created 09/15/2020 08:54:32 by BOVINE320\Jeff-->
      <Name>default</Name>
      <ViewSelectedBy>
        <TypeName>PSRemoteSessionUser</TypeName>
      </ViewSelectedBy>
      <GroupBy>
        <PropertyName>Computername</PropertyName>
      </GroupBy>
      <TableControl>
        <!--Delete the AutoSize node if you want to use the defined widths.-->
        <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
            <Label>SessionConfiguration</Label>
            <Width>23</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Address</Label>
            <Width>14</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Username</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>State</Label>
            <Width>12</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>Runtime</Label>
            <Width>15</Width>
            <Alignment>left</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>MemMB</Label>
            <Width>12</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
          <TableColumnHeader>
            <Label>PID</Label>
            <Width>12</Width>
            <Alignment>right</Alignment>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>SessionConfiguration</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>RemoteIPAddress</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Username</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>State</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>ShellRunTime</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <ScriptBlock>$_.Memory/.1MB -as [int]</ScriptBlock>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>ProcessID</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

局限性

我现在有一个 PowerShell 工具,可以运行它来查看谁连接到服务器以及这些会话的状态。

[玩转系统] 应对 WSMan PowerShell 挑战

下一步

我希望您能尝试一下挑战,并密切关注 Iron Scripter 网站以应对未来的挑战。它们是测试您的知识和扩展您的技能的好方法。如果您想了解有关 PowerShell 的更多信息,可以查看 Pluralsight 提供的我的 PowerShell 远程处理基础课程。或者访问我的书籍和培训页面。

我希望您发现本文内容丰富且有用。随时欢迎提出意见和反馈。

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

取消回复欢迎 发表评论:

关灯