PowerShell 技能连载 - 密码混淆器脚本

曾经需要将密码保存在脚本中?曾经需要自动弹出一个身份验证对话框?对于前者,将密码和其它身份信息存储在脚本中是很糟糕的对于后者,如果您这么做了的话,至少能使黑客更难于窃取信息。

以下是一个脚本生成器。运行它,并且输入一个域/用户名和密码,脚本生成器会为您生成一段新脚本。

$pwd = Read-Host 'Enter Password'
$user = Read-Host 'Enter Username'
$key = 1..32 |
  ForEach-Object { Get-Random -Maximum 256 }

$pwdencrypted = $pwd |
  ConvertTo-SecureString -AsPlainText -Force |
  ConvertFrom-SecureString -Key $key

$text = @()
$text += '$password = "{0}"' -f ($pwdencrypted -join ' ')
$text += '$key = "{0}"' -f ($key -join ' ')
$text += '$passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split(" "))'
$text += '$cred = New-Object system.Management.Automation.PSCredential("{0}", $passwordSecure)' -f $user
$text += '$cred'

$newFile = $psise.CurrentPowerShellTab.Files.Add()
$newFile.Editor.Text = $text | Out-String

这段脚本包含混淆过的密码脚本,看起来大概类似这样:

$password = "76492d1116743f0423413b16050a5345MgB8AFcAMABGAEIANAB1AGEAdQA3ADUASABhAE0AMgBNADUAUwBnAFYAYQA1AEEAPQA9AHwAMgAyAGIAZgA1ADUAZgA0ADIANAA0ADUANwA2ADAAMgA5ADkAZAAxAGUANwA4ADUAZQA4ADkAZAA1AGMAMAA2AA=="
$key = "246 185 95 207 87 105 146 74 99 163 58 194 93 229 80 241 160 35 68 220 130 193 84 113 122 155 208 49 152 86 85 178"
$passwordSecure = ConvertTo-SecureString -String $password -Key ([Byte[]]$key.Split(" "))
$cred = New-Object system.Management.Automation.PSCredential("test\tobias", $passwordSecure)
$cred

当您运行它,它将生成一个 Credential 对象,您可以立即将它用于身份验证。只要将它传给一个需要 Credential 对象的形参即可。

再强调一下,这并不是安全的。但是要想获取密码的明文还需要更多点知识才行。

PowerShell 技能连载 - 检查磁盘分区和数据块大小

WMI 是一个装满信息的宝库。以下这行代码将读取本地分区以及它们的数据块大小信息:

Get-WmiObject -Class Win32_Diskpartition  |
  Select-Object -Property __Server, Caption, BlockSize

使用 Get-WmiObject-ComputerName 参数可以对一台或多台机器远程执行同样的操作。

要查看其它所有的 WMI 类,您可以替换掉 Win32_DiskPartition,试试以下的代码:

Get-WmiObject -Class Win32_* -List |
  Where-Object { ($_.Qualifiers | Select-Object -ExpandProperty Name) -notcontains 'Association' } |
  Where-Object { $_.Name -notlike '*_Perf*' }

PowerShell 技能连载 - 将Excel导出的CSV转换为UTF-8编码

当您导出 Microsoft Excel 数据表到 CSV 文件时,Excel缺省将保存为 ANSI 编码的 CSV 文件。这是很糟糕的,因为当您用 Import-Csv 导入数据到 PowerShell 中时,特殊字符将会截断(译者注:例如中文出现乱码)。

要确保特殊字符不会丢失,您必须确保导入数据之前 CSV 文件采用的是 UTF-8 编码:

$Path = 'c:\temp\somedata.csv'
(Get-Content -Path $Path) | Set-Content -Path $Path -Encoding UTF8

PowerShell 技能连载 - 查找所有用户脚本

有些时候我们会疑惑当 PowerShell 启动的时候,将执行哪些启动脚本。它们数量很多,而且各不相同,要看您运行的是 PowerShell 控制台,ISE,还是其他宿主。

然而,了解您的用户脚本是十分重要的。它们决定了应用到 PowerShell 环境的配置。

这个 Get-PSProfileStatus 函数列出了所有宿主(PowerShell 环境)可能用到的的启动脚本。它也显示了哪些脚本是物理存在的。

function Get-PSProfileStatus
{
    $profile |
      Get-Member -MemberType NoteProperty |
      Select-Object -ExpandProperty Name |
      ForEach-Object {
        $_, (Split-Path $profile.$_ -Leaf), (Split-Path $profile.$_),
                              (Test-Path -Path $profile.$_) -join ',' |
          ConvertFrom-Csv -Header Profile, FileName, FolderName, Present
        }
}

Get-PSProfileStatus

结果看起来类似这样:

将结果用管道输出到 Out-GridView 来查看,避免截断字符被截断:

Get-PSProfileStatus | Out-GridView

PowerShell 技能连载 - 通过CSV创建对象

有多种方法可以创建自定义对象。以下是一种创新的办法,在很多种场景中都很有效:创建一个逗号分隔,每行表示一个值的列表文本,然后用 ConvertFrom-Csv 来创建对象:

for($x=0; $x -lt 20; $x++)
{
    ($x,(Get-Random),(Get-Date) -join ',') | ConvertFrom-Csv -Header ID, RandomNumber, Date
}

不过,这种做法效率并不是很高。还有三种其它方法可以用来创建对象。分别用 Measure-Command 测量创建 2000 个对象所消耗的时间:

Measure-Command {
    for($x=0; $x -lt 2000; $x++)
    {
        ($x,(Get-Random),(Get-Date) -join ',') | ConvertFrom-Csv -Header ID, RandomNumber, Date
    }
}

Measure-Command {
    for($x=0; $x -lt 2000; $x++)
    {
        $obj = 1 | Select-Object -Property ID, RandomNumber, Date
        $obj.ID = $x
        $obj.RandomNumber = Get-Random
        $obj.Date = Get-Date
        $obj
    }
}

Measure-Command {
    for($x=0; $x -lt 2000; $x++)
    {
        [PSObject]@{
            ID = $x
            RandomNumber = Get-Random
            Date = Get-Date
        }
    }
}

Measure-Command {
    for($x=0; $x -lt 2000; $x++)
    {
        [Ordered]@{
            ID = $x
            RandomNumber = Get-Random
            Date = Get-Date
        }
    }
}

如结果所示,最后两种方法的效率是 CSV 方法的大约三倍。在我们的测试系统上,所有的测试都在一秒之内完成,所以现实环境中影响并不大。

请挑选一个您自己最喜欢的方式——不过请注意最后一个例子需要 PowerShell 3.0 或更高的版本。

PowerShell 技能连载 - 通过关键词查找脚本

随着您硬盘上的 PowerShell 脚本数量的增多,要想找到您想要的脚本会变得越来越困难。以下是一个叫做 Find-Script 的工具函数。只要传入一个关键词,PowerShell 将会在您的个人文件夹下找出所有包含该关键词的脚本。

查找的结果将在一个 GridView 窗口中显示,您可以选中其中的文件,按下确认按钮以后将用 ISE 编辑器打开这些文件。

function Find-Script
{
    param
    (
        [Parameter(Mandatory=$true)]
        $Keyword,

        $Maximum = 20,
        $StartPath = $env:USERPROFILE
    )

    Get-ChildItem -Path $StartPath -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue |
      Select-String -SimpleMatch -Pattern $Keyword -List |
      Select-Object -Property FileName, Path, Line -First $Maximum |
      Out-GridView -Title 'Select Script File' -PassThru |
      ForEach-Object { ise $_.Path }
}

默认情况下,Find-Script 只返回满足搜索条件的前 20 个脚本。您可以通过 -Maximum-StartPath 参数来改变最大搜索条数和搜索位置。

PowerShell 技能连载 - 设置显示器亮度

如果您的显示驱动程序支持WMI,那么您可以用PowerShell改变显示器的亮度——甚至是远程的计算机!

以下是实现改变显示器亮度的函数:

function Set-MonitorBrightness
{
    param
    (
        [Parameter(Mandatory=$true)]
        [Int][ValidateRange(0,100)]
        $Value,

        $ComputerName,
        $Credential
    )

    $null = $PSBoundParameters.Remove('Value')

    $helper = Get-WmiObject -Namespace root/WMI -Class WmiMonitorBrightnessMethods @PSBoundParameters
    $helper.WmiSetBrightness(1, $Value)
}

只需要指定一个0-100之间的值,您就可以看到显示器亮度发生改变。为 -ComputerName 参数指定远程计算机名或IP地址(均支持多个),然后您远程的同事们会惊讶地发现去吃午餐的时候显示器都变暗了!当然,远程操作WMI需要本地管理员权限,并且为防火墙设置了允许远程管理的规则。

如果提示“不支持”的错误提示信息,那么说明您的显示驱动程序不支持WMI。

这是“有趣”的部分:模拟一个古怪的显示效果:

for($x=0; $x -lt 20; $x++)
{
    Set-MonitorBrightness -Value (Get-Random -Minimum 20 -Maximum 101)
    Start-Sleep -Seconds 1
}

PowerShell 技能连载 - 检测显示器亮度

如果您想检查您当前的显示器亮度(当然,尤其是针对笔记本电脑),以下是一个快捷的函数:

function Get-MonitorBrightness
{
    param($ComputerName, $Credential)

    Get-WmiObject -Namespace root/WMI -Class WmiMonitorBrightness @PSBoundParameters |
        Select-Object -Property PSComputerName, CurrentBrightness, Levels
}

它甚至支持 -ComputerName-Credential,所以您也可以查询远程的主机。

如果您为 -ComputerName 参数传入一个用逗号分隔的主机名或IP地址列表,您将获得所有具有local Admin权限的主机的执行结果。

PowerShell 技能连载 - 创建符号链接

符号链接使用起来很像“普通”的链接文件(*.lnk):它们可以虚拟地指向任何文件或者文件夹,甚至UNC路径。和lnk文件不同的是,创建符号链接需要完整管理员权限,并且用户不可以存取符号链接属性。

以下是一个创建符号链接的函数:

function New-SymbolicLink
{
    param
    (
        [Parameter(Mandatory=$true)]
        $OriginalPath,

        [Parameter(Mandatory=$true)]
        $MirroredPath,

        [ValidateSet('File', 'Directory')]
        $Type='File'
    )

    if(!([bool]((whoami /groups) -match "S-1-16-12288") ))
    {
        Write-Warning 'Must be an admin'
        break
    }
    $signature = '
        [DllImport("kernel32.dll")]
        public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);
        '
    Add-Type -MemberDefinition $signature -Name Creator -Namespace SymbolicLink

    $Flags = [Int]($Type -eq 'Directory')
    [SymbolicLink.Creator]::CreateSymbolicLink($MirroredPath, $OriginalPath,$Flags)
}

$downloads = "$env:userprofile\Downloads"
$desktop = "$env:userprofile\Desktop\MyDownloads"

New-SymbolicLink -OriginalPath $downloads -MirroredPath $desktop -Type Directory

当您(以管理员身份)运行这段代码时,它将使您能在桌面上访问下载文件夹。请右击符号链接并选择属性,并和“普通”的*.link文件做对比。

PowerShell 技能连载 - 检查管理员权限

以下通过一个非常规的办法实现检查一段脚本是否以管理员权限运行(通过提升UAC),这体现了PowerShell强大的灵活性:

function Test-Admin { [bool]((whoami /groups) -match "S-1-16-12288") }

它的基本原理是检查当前用户是否是高完整性级别用户组的成员。该用户组是专门针对提升权限的管理员设置的。

如果您不想使用本地命令(whoami.exe)的话,还可以使用更贴近PowerShell(或.NET)的方法,如以下代码所示:

function Test-Admin {

    $id = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())

    $id.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)

}