Chapter 23 高级远程配置

接续《PowerShell实战指南 Chapter 13-17》的第13章。

一台计算机可以包含多个端点。在PowerShell中,端点也被称为session configurations。如上图,在64位系统上也会开启32位端点,只不过64位是默认的。

使用其他端点:

创建自定义端点

分两步:

  • 通过New-PSSessionConfigurationFile创建新的会话配置文件(.pssc
  • 通过Register-PSSessionConfiguration载入该文件

举例如下(未实验):创建一个只有域中HelpDesk组的成员可以访问的端点。在端点内,只允许他们看到与网络适配器相关的命令,但不可运行。另外,我们配置该端点使用我们提供的备用凭据:

New-PSSessionConfigurationFile -Path C:\HelpDeskEndpoint.pssc `
    -ModulesToImport NetAdapter `
    -SessionType RestrictedRemoteServer `
    -CompanyName "Our Company" `
    -Author "Don Jones" `
    -Description "New adapter commands for use by help desk" `
    -PowerShellVersion "3.0"

Register-PSSessionConfiguration -Name "HelpDesk" `
    -Path .\HelpDeskEndpoint.pssc `
    -RunAsCredential COMPANY\HelpDeskProxyAdmin `
    -ShowSecurityDescriptorUI

Chapter 24 使用正则表达式解析文本文件

需要注意的是,在PowerShell内部我们往往不需要使用正则表达式,因为那些对象都有相应的属性供我们筛选。正则表达式往往在处理外部大量文本时使用。

通过help about_regular_expressions查询更多。

使用-Match-Cmatch(区分大小写)来做匹配:

通过Select-String使用正则表达式

下面我们将在IIS日志文件中做测试:

  • 查找40x错误作为开头的日志并生成报表
Get-ChildItem -Filter *.log -Recurse |
Select-String -Pattern "\s40[0-9]\s" |
Format-Table Filename,LineNumber,Line -Wrap

  • 查找所有被基于Gecko的浏览器访问过的文件,且使用的操作系统为Windows NT 6.2

也就是要查找如下的字符串:

(Windows+NT+6.2;+WOW64;+rv:11.0)+Gecko

其中,WOW64不必要。

Get-ChildItem -Filter *.log -Recurse |
Select-String -Pattern "6\.2;[\w\W]+\+Gecko"

其他的例子如:

Get-EventLog -LogName Security |
where {$_.eventid -eq 4264 -and $_.message -match "WIN[\W\w]+TM[234][0-9]\$"}

可以看到,正则表达式在PowerShell中是无处不在的。

练习

  • 获取计算机中所有非微软进程,显示ID、名称和公司名称

P.S. https://www.regextester.com 测试你的正则表达式。

Chapter 25-28 旅程的最后

Profile

我们在四个不同的地方添加profile配置文件,并在其中添加一个打印语句,然后重新启动PowerShell,观察不同载入顺序:

# 1
Write-Host '$PSHOME/Profile.ps1'
# 2
Write-Host '$PSHOME/Micrsoft.PowerShell_Profile.ps1'
# 3
Write-Host '$HOME/Documents/WindowsPowerShell/Profile.ps1'
# 4
Write-Host '$HOME/Documents/WindowsPowerShell/Microsoft.PowerShell_Profile.ps1'

关于profile的配置,可以参考help about_profiles

运算符

  • 类型转换

  • 类型判断

  • 替换字符(串)

  • 字符串与数组互相转化

  • 包含与存在

脚本块

$Block = {Get-Process | Sort -Property VM -Descending}
# invoke
&$Block

参考help about_script_block

脚本

练习

理解以下脚本:

function get-LastOn {
<#
.DESCRIPTION
Tell me the most recent event log entries for logon or logoff.
.BUGS
Blank 'computer' column
.EXAMPLE
get-LastOn -computername server1 | Sort-Object time -Descending |
Sort-Object id -unique | format-table -AutoSize -Wrap
ID
--
LOCAL SERVICE
NETWORK SERVICE NT AUTHORITY
SYSTEM          NT AUTHORITY
Computer Time
-------- ----
         4/3/2012 11:16:39 AM
         4/3/2012 11:16:39 AM
         4/3/2012 11:16:02 AM
Domain
------
NT AUTHORITY
Sorting -unique will ensure only one line per user ID, the most recent.
Needs more testing
.EXAMPLE
PS C:\Users\administrator> get-LastOn -computername server1 -newest 10000
 -maxIDs 10000 | Sort-Object time -Descending |
 Sort-Object id -unique | format-table -AutoSize -Wrap
ID              Domain
--              ------
Administrator   USS
ANONYMOUS LOGON NT AUTHORITY
LOCAL SERVICE   NT AUTHORITY
NETWORK SERVICE NT AUTHORITY
student         WIN7
SYSTEM          NT AUTHORITY
USSDC$          USS
WIN7$           USS
PS C:\Users\administrator>
Computer Time
-------- ----
         4/11/2012 10:44:57 PM
         4/3/2012 8:19:07 AM
         10/19/2011 10:17:22 AM
         4/4/2012 8:24:09 AM
         4/11/2012 4:16:55 PM
         10/18/2011 7:53:56 PM
         4/11/2012 9:38:05 AM
         10/19/2011 3:25:30 AM
.EXAMPLE
get-LastOn -newest 1000 -maxIDs 20
Only examines the last 1000 lines of the event log
.EXAMPLE
get-LastOn -computername server1| Sort-Object time -Descending |
Sort-Object id -unique | format-table -AutoSize -Wrap
#>

    param (
        [string]$ComputerName = 'localhost',
        [int]$Newest = 5000,
        [int]$maxIDs = 5,
        [int]$logonEventNum = 4624, # log on successfully
        [int]$logoffEventNum = 4647
    )

    $eventsAndIDs = Get-EventLog -LogName security -Newest $Newest |
        Where-Object {$_.instanceid -eq $logonEventNum -or $_.instanceid -eq $logoffEventNum} |
        Select-Object -Last $maxIDs -Property TimeGenerated,Message,ComputerName

    foreach ($event in $eventsAndIDs) {
        $id = ($event |
            parseEventLogMessage |
            where-Object {$_.fieldName -eq "Account Name"}  |
            Select-Object -last 1).fieldValue

        $domain = ($event |
            parseEventLogMessage |
            where-Object {$_.fieldName -eq "Account Domain"}  |
            Select-Object -last 1).fieldValue

        # hashtable
        $props = @{'Time'=$event.TimeGenerated;
            'Computer'=$ComputerName;
            'ID'=$id
            'Domain'=$domain}

        $output_obj = New-Object -TypeName PSObject -Property $props

        Write-Output $output_obj
    }
}

function parseEventLogMessage()
{
    [CmdletBinding()]
    param (
        [parameter(ValueFromPipeline=$True,Mandatory=$True)]
        [string]$Message
    )

    $eachLineArray = $Message -split "`n"

    foreach ($oneLine in $eachLineArray) {
        write-verbose "line:_$oneLine_"
        $fieldName,$fieldValue = $oneLine -split ":", 2
    }
}

Get-LastOn

该脚本用于从安全日志中获取用户的登录登出信息。

我的测试中,发现它有几点瑕疵:

  • 没有处理异常(暂不修改)
  • 无法接受外部传入的参数(在后面修改)
  • 无法使用help(在后面修改)

最终,我给修改成如下形式:

<#
.DESCRIPTION
Tell me the most recent event log entries for logon or logoff.
.BUGS
Blank 'computer' column
.EXAMPLE
get-LastOn -computername server1 | Sort-Object time -Descending |
Sort-Object id -unique | format-table -AutoSize -Wrap
ID
--
LOCAL SERVICE
NETWORK SERVICE NT AUTHORITY
SYSTEM          NT AUTHORITY
Computer Time
-------- ----
         4/3/2012 11:16:39 AM
         4/3/2012 11:16:39 AM
         4/3/2012 11:16:02 AM
Domain
------
NT AUTHORITY
Sorting -unique will ensure only one line per user ID, the most recent.
Needs more testing
.EXAMPLE
PS C:\Users\administrator> get-LastOn -computername server1 -newest 10000
 -maxIDs 10000 | Sort-Object time -Descending |
 Sort-Object id -unique | format-table -AutoSize -Wrap
ID              Domain
--              ------
Administrator   USS
ANONYMOUS LOGON NT AUTHORITY
LOCAL SERVICE   NT AUTHORITY
NETWORK SERVICE NT AUTHORITY
student         WIN7
SYSTEM          NT AUTHORITY
USSDC$          USS
WIN7$           USS
PS C:\Users\administrator>
Computer Time
-------- ----
         4/11/2012 10:44:57 PM
         4/3/2012 8:19:07 AM
         10/19/2011 10:17:22 AM
         4/4/2012 8:24:09 AM
         4/11/2012 4:16:55 PM
         10/18/2011 7:53:56 PM
         4/11/2012 9:38:05 AM
         10/19/2011 3:25:30 AM
.EXAMPLE
get-LastOn -newest 1000 -maxIDs 20
Only examines the last 1000 lines of the event log
.EXAMPLE
get-LastOn -computername server1| Sort-Object time -Descending |
Sort-Object id -unique | format-table -AutoSize -Wrap
#>

param (
    [string]$ComputerName = 'localhost',
    [int]$Newest = 5000,
    [int]$maxIDs = 5,
    [int]$logonEventNum = 4624, # log on successfully
    [int]$logoffEventNum = 4647
)

function get-LastOn([string]$ComputerName, [int]$Newest, [int]$maxIDs, [int]$logonEventNum, [int]$logoffEventNum) {

    $eventsAndIDs = Get-EventLog -LogName security -Newest $Newest |
        Where-Object {$_.instanceid -eq $logonEventNum -or $_.instanceid -eq $logoffEventNum} |
        Select-Object -Last $maxIDs -Property TimeGenerated,Message,ComputerName

    foreach ($event in $eventsAndIDs) {
        $id = ($event |
            parseEventLogMessage |
            where-Object {$_.fieldName -eq "Account Name"}  |
            Select-Object -last 1).fieldValue

        $domain = ($event |
            parseEventLogMessage |
            where-Object {$_.fieldName -eq "Account Domain"}  |
            Select-Object -last 1).fieldValue

        # hashtable
        $props = @{'Time'=$event.TimeGenerated;
            'Computer'=$ComputerName;
            'ID'=$id
            'Domain'=$domain}

        $output_obj = New-Object -TypeName PSObject -Property $props

        Write-Output $output_obj
    }
}

function parseEventLogMessage()
{
    [CmdletBinding()]
    param (
        [parameter(ValueFromPipeline=$True,Mandatory=$True)]
        [string]$Message
    )

    $eachLineArray = $Message -split "`n"

    foreach ($oneLine in $eachLineArray) {
        write-verbose "line:_$oneLine_"
        $fieldName,$fieldValue = $oneLine -split ":", 2
    }
}

get-LastOn -ComputerName $ComputerName -Newest $Newest -maxIDs $maxIDs -logonEventNum $logonEventNum -logoffEventNum $logoffEventNum

不过,如果上述脚本只是作为模块使用的话,倒不必这样做。

运行截图如下: