PowerShell 技能连载 - 发现动态参数

在前一个技能中我们展示了如何查找暴露了动态参数的 cmdlet。现在让我们来探索什么事动态参数。这个 Get-CmdletDynamicParameter 函数将返回一个动态参数的列表和它们的缺省值:

#requires -Version 2
function Get-CmdletDynamicParameter
{
  param (
    [Parameter(ValueFromPipeline = $true,Mandatory = $true)]
    [String]
    $CmdletName
  )

  process
  {
    $command = Get-Command -Name $CmdletName -CommandType Cmdlet
    if ($command)
    {
      $cmdlet = New-Object -TypeName $command.ImplementingType.FullName
      if ($cmdlet -is [Management.Automation.IDynamicParameters])
      {
        $flags = [Reflection.BindingFlags]'Instance, Nonpublic'
        $field = $ExecutionContext.GetType().GetField('_context', $flags)
        $context = $field.GetValue($ExecutionContext)
        $property = [Management.Automation.Cmdlet].GetProperty('Context', $flags)
        $property.SetValue($cmdlet, $context, $null)

        $cmdlet.GetDynamicParameters()
      }
    }
  }
}

Get-CmdletDynamicParameter -CmdletName Get-ChildItem

该函数使用一些黑客的办法来暴露动态参数,这种方法是受到 Dave Wyatt 的启发。请参见他的文章 https://davewyatt.wordpress.com/2014/09/01/proxy-functions-for-cmdlets-with-dynamic-parameters/

PowerShell 技能连载 - 查找带动态参数的 cmdlet

有些 cmdlet 暴露了动态参数。它们只在特定的环境下可用。例如 Get-ChildItem 只在当前的位置是文件系统路径(并且是 PowerShell 3.0 以上版本)时才暴露 -File-Directory 参数。

要查找所有带动态参数的 cmdlet,请试试这段代码:

#requires -Version 2

$cmdlets = Get-Command -CommandType Cmdlet

$cmdlets.Count

$loaded = $cmdlets |
Where-Object { $_.ImplementingType }

$loaded.Count

$dynamic = $loaded |
Where-Object {
    $cmdlet = New-Object -TypeName $_.ImplementingType.FullName
    $cmdlet -is [System.Management.Automation.IDynamicParameters]
  }

$dynamic.Count

$dynamic | Out-GridView

您将只会获得已加载并且包含动态参数的 cmdlet。

PowerShell 技能连载 - 改变 ISE 缩放比例

PowerShell ISE 的右下角有一个缩放滑竿,您也可以用 PowerShell 代码来控制它。

所以,您可以在 $profile 脚本中设置缺省值:

$psise.Options.Zoom = 120

或者,可以写一些代码来戏弄您的同事:

#requires -Version 2

$zoom = $psise.Options.Zoom

# slide in
for ($i = 20; $i -lt 200; $i++)
{
  $psise.Options.Zoom = $i
}

# slide out
for ($i = 199; $i -gt 20; $i--)
{
  $psise.Options.Zoom = $i
}

# random whacky
1..10 |
ForEach-Object {
  $psise.Options.Zoom = (Get-Random -Minimum 30 -Maximum 400)
  Start-Sleep -Milliseconds (Get-Random -Minimum 100 -Maximum 400)
}

$psise.Options.Zoom = $zoom

PowerShell 技能连载 - 在任意 Powershell 版本中解压 ZIP 文件

如果您没有安装 PowerShell 5.0,并且没有安装 .NET Framework 4.5,以下是一个使用 Windows 原生功能解压 ZIP 文件的办法。

不过,如果您安装了资源管理器自定义的 ZIP 文件扩展,这个方法可能不能用。

$Source = 'C:\somezipfile.zip'
$Destination = 'C:\somefolder'
$ShowDestinationFolder = $true

if ((Test-Path $Destination) -eq $false)
{
  $null = mkdir $Destination
}

$shell = New-Object -ComObject Shell.Application
$sourceFolder = $shell.NameSpace($Source)
$destinationFolder = $shell.NameSpace($Destination)
$DestinationFolder.CopyHere($sourceFolder.Items())

if ($ShowDestinationFolder)
{
  explorer.exe $Destination
}

这个方法的好处是在需要覆盖文件的时候,会弹出 shell 的对话框。这个方法也可以解压 CAB 文件。

PowerShell 技能连载 - 在 PowerShell 3.0 和 4.0 中解压 ZIP 文件

PowerShell 5.0 中引入了 ZIP 文件支持,但是如果您安装了 .NET Framework 4.5 并且希望更多地控制解压的过程,请试试这个方法:

#requires -Version 2
# .NET Framework 4.5 required!

Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop

$Source = 'C:\somezipfile.zip'
$Destination = 'C:\somefolder'
$Overwrite = $true
$ShowDestinationFolder = $true

if ((Test-Path $Destination) -eq $false)
{
 $null = mkdir $Destination
}

$Content = [IO.Compression.ZipFile]::OpenRead($Source).Entries
$Content |
 ForEach-Object -Process {
    $FilePath = Join-Path -Path $Destination -ChildPath $_
                [IO.Compression.ZipFileExtensions]::ExtractToFile($_,$FilePath,$Overwrite)
            }
if ($ShowDestinationFolder)
{
    explorer.exe $Destination
}

PowerShell 技能连载 - 解压 ZIP 文件

在 PowerShell 5.0 中,有一个新的 cmdlet 可以解压 ZIP 文件:

#requires -Version 5

$Source = 'C:\somezipfile.zip'
$Destination = 'C:\somefolder'
$Overwrite = $true
$ShowDestinationFolder = $true

Expand-Archive -Path $Source -DestinationPath $Destination -Force:$Overwrite

if ($ShowDestinationFolder)
{
  explorer.exe $Destination
}

PowerShell 技能连载 - 查找打开了 PowerShell 远程操作功能的计算机

在前一个技能中我们演示了如何如何测试一台计算机的端口。在安装了 Microsoft 免费的 RSAT 工具之后,您可以查询您的 Active Directory,并获取所有计算机用户的列表,或指定范围内的所有计算机账户(例如用 -SearchBase 限制在一个特定的 OU 中搜索)。

下一步,您可以使用该端口来测试这些计算机是否在线,以及 PowerShell 远程操作端口 5985 是否打开:

#requires -Version 1 -Modules ActiveDirectory
function Test-Port
{
    Param([string]$ComputerName,$port = 5985,$timeout = 1000)

    try
    {
        $tcpclient = New-Object -TypeName system.Net.Sockets.TcpClient
        $iar = $tcpclient.BeginConnect($ComputerName,$port,$null,$null)
        $wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)
        if(!$wait)
        {
            $tcpclient.Close()
            return $false
        }
        else
        {
            # Close the connection and report the error if there is one

            $null = $tcpclient.EndConnect($iar)
            $tcpclient.Close()
            return $true
        }
    }
    catch
    {
        $false
    }
}

Get-ADComputer -Filter * |
Select-Object -ExpandProperty dnsHostName |
ForEach-Object {
    Write-Progress -Activity 'Testing Port' -Status $_
} |
Where-Object -FilterScript {
    Test-Port -ComputerName $_
}

PowerShell 技能连载 - 测试一个网络端口

这个 Test-Port 的测试函数可以通过一个网络端口测试一台远程的机器。它传入一个远程机器名(或 IP 地址),以及可选的端口号和和超时值。

缺省端口号是 5985,改端口用于 PowerShell 远程操作。缺省的超时值是 1000ms(1 秒)。

#requires -Version 1
function Test-Port
{
    Param([string]$ComputerName,$port = 5985,$timeout = 1000)

    try
    {
        $tcpclient = New-Object -TypeName system.Net.Sockets.TcpClient
        $iar = $tcpclient.BeginConnect($ComputerName,$port,$null,$null)
        $wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)
        if(!$wait)
        {
            $tcpclient.Close()
            return $false
        }
        else
        {
            # Close the connection and report the error if there is one

            $null = $tcpclient.EndConnect($iar)
            $tcpclient.Close()
            return $true
        }
    }
    catch
    {
        $false
    }
}

所以如果您希望知道一台远程计算机是否启用了 PowerShell 远程操作,您只需要运行:

PS> Test-Port -ComputerName TestServer
False

由于缺省的超时值是 1 秒,您最多等待 1 秒就能等到响应。

PowerShell 技能连载 - 查找登录的用户

在前一个技能里我们介绍了如何查找物理上登录的用户。在这个技能中您将学习到如何列出当前登录到本地系统的所有用户。这包括了通过 RDP 及其它方式连上的用户:

#requires -Version 1
function Get-LoggedOnUserSession
{
    param
    (
        $ComputerName,
        $Credential
    )

    Get-WmiObject -Class Win32_LogonSession @PSBoundParameters |
    ForEach-Object {
        $_.GetRelated('Win32_UserAccount') |
        Select-Object -ExpandProperty Caption
    } |
    Sort-Object -Unique
}

执行 Get-LoggedOnUserSession 命令将得到当前登录到机器上的所有用户。如指定了 -Credential(域名\用户名)参数,可以访问远程机器。

PowerShell 技能连载 - 查找物理登录的用户

一台机器上只能有一个物理登录的用户。物理登录的用户意味着正坐在机器旁边的那个用户。

这个 PowerShell 函数能返回本地或远程系统物理登录的用户。要访问远程系统,您可能需要远程系统的本地管理员权限,并且确保防火墙已配置成允许连接。

#requires -Version 1
function Get-LoggedOnUser
{
    param
    (
        $ComputerName,
        $Credential
    )

    Get-WmiObject -Class Win32_ComputerSystem @PSBoundParameters |
    Select-Object -ExpandProperty UserName
}

运行 Get-LoggedOnUser 命令后能够获得本机上物理登录的用户名。指定 -ComputerName(或者 -Credential)参数可以获得远程机器上物理登录的用户名。