PowerShell 技能连载 - 创建日历(和日期列表)

以下是一段创建 DateTime 集合的脚本片段。只需要指定年和月,脚本将会针对该月的每一天创建一个 DateTime 对象:

$month = 8
$year = 2013

1..[DateTime]::DaysInMonth($year,$month) |
  ForEach-Object { Get-Date -Day $_ -Month $month -Year $year }

这段代码十分有用:只要加一个日期过滤器,您就可以过滤出工作日来。它将列出指定月份的所有周一至周五(因为它排除了 weekday 0(星期日)和 weekday 6(星期六)):

$month = 8
$year = 2013

1..[DateTime]::DaysInMonth($year,$month) |
  ForEach-Object { Get-Date -Day $_ -Month $month -Year $year } |
  Where-Object { 0,6 -notcontains $_.DayOfWeek }

类似地,以下代码将统计指定月份所有星期三和星期五的天数:

$month = 8
$year = 2013

$days = 1..[DateTime]::DaysInMonth($year,$month) |
  ForEach-Object { Get-Date -Day $_ -Month $month -Year $year } |
  Where-Object { 3,5 -contains $_.DayOfWeek }

$days
"There are {0} Wednesdays and Fridays" -f $days.Count

PowerShell 技能连载 - 发生了什么?

经常地,您需要用 PowerShell 来获取数据,并且您需要提取信息的一部分并且把它们用于报表。类似如下:

$serial = Get-WmiObject -Class Win32_OperatingSystem |
  Select-Object -Property SerialNumber

"Serial Number is $serial"

但是上述代码产生的结果如下:

Serial Number is @{SerialNumber=00261-30000-00000-AA825}

当您查看 $serial 的值,它看起来似乎很正常:

PS> $serial

SerialNumber
------------
00261-30000-00000-AA825

但问题出在列头(译者注:我们只需要 SerialNumber 的值,而不是需要一个包含 SerialNumber 属性的临时对象)。您可以用 Select-Object 只选出一列,用 -ExpandProperty 而不是 -Property 就可以消除列头:

$serial = Get-WmiObject -Class Win32_OperatingSystem |
  Select-Object -ExpandProperty SerialNumber

"Serial Number is $serial"

现在,一切正常了:

Serial Number is 00261-30000-00000-AA825

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 对象的形参即可。

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

快速替换文本文件中的字符串

不用开什么vim、emac、UltraEdit、Eclipse之类的编辑器了,PowerShell可以帮助手无寸铁的您快速地替换文本文件中的字符串:

dir *.txt -Recurse | % {
    (gc $_ -Raw) | % { $_ `
        -creplace '111', 'AAA' `
        -creplace '222', 'BBB' `
        -creplace '333', 'CCC'
    } | sc $_
}

注意 -creplace 区分大小写,-replace 不区分大小写。并且它们支持正则表达式!

在PowerShell中以管理员身份运行程序

对于已知的需要以管理员身份运行的命令,我们可以通过这个 Invoke-Admin 函数运行。这个函数确保以管理员身份运行一个程序。如果不是以管理员身份运行,则将弹出 UAC 对话框。

function Invoke-Admin() {
    param ( [string]$program = $(throw "Please specify a program" ),
            [string]$argumentString = "",
            [switch]$waitForExit )

    $psi = new-object "Diagnostics.ProcessStartInfo"
    $psi.FileName = $program
    $psi.Arguments = $argumentString
    $psi.Verb = "runas"
    $proc = [Diagnostics.Process]::Start($psi)
    if ( $waitForExit ) {
        $proc.WaitForExit();
    }
}

来源:Showing the UAC prompt in PowerShell if the action requires elevation

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 参数来改变最大搜索条数和搜索位置。