PowerShell 技能连载 - 创建动态脚本块

脚本块是一段可执行的 PowerShell 代码。您通常可以将语句包裹在大括号中创建脚本块。

若要在脚本中动态创建脚本块,以下是将一个字符串转换为脚本块的方法。

$scriptblock = [ScriptBlock]::Create('notepad')

通过这种方法,您的代码首先以字符串的形式创建代码,然后将字符串转换为脚本块,然后将脚本块传递给您需要的 cmdlet(例如 Invoke-Command):

PS> Invoke-Command -ScriptBlock 'notepad'
Cannot convert  the "notepad" value of type "System.String" to type
"System.Management.Automation.ScriptBlock". (raised by:  Invoke-Command)

PS> Invoke-Command -ScriptBlock ([ScriptBlock]::Create('notepad'))

PowerShell 技能连载 - 管理 NTFS 权限继承

缺省情况下,文件和文件夹从它们的父节点继承权限。要停用继承,并且只保留显式权限,请做以下两件事情:增加你需要显式权限,然后禁止继承。

这个示例代码创建了一个名为“_PermissionNoInheritance_”的文件夹,然后为当前用户赋予读权限,为管理员组赋予完整权限,并且禁止继承。

# create folder
$Path = 'c:\PermissionNoInheritance'
$null = New-Item -Path $Path -ItemType Directory -ErrorAction SilentlyContinue

# get current permissions
$acl = Get-Acl -Path $path

# add a new permission for current user
$permission = $env:username, 'Read,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)

# add a new permission for Administrators
$permission = 'Administrators', 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)

# disable inheritance
$acl.SetAccessRuleProtection($true, $false)

# set new permissions
$acl | Set-Acl -Path $path

PowerShell 技能连载 - 移除非继承的 NTFS 权限

在前一个例子中,我们演示了如何向已有的文件夹添加新的 NTFS 权限。如果您希望重置权限并且移除所有之前所有非继承的 NTFS 权限,以下是示例代码:

# make sure this folder exists
$Path = 'c:\sampleFolderNTFS'

# get explicit permissions
$acl = Get-Acl -Path $path
$acl.Access |
  Where-Object { $_.isInherited -eq $false } |
  # ...and remove them
  ForEach-Object { $acl.RemoveAccessRuleAll($_) }

# set new permissions
$acl | Set-Acl -Path $path

PowerShell 技能连载 - 获取非继承的 NTFS 权限

要查看某个文件或者文件夹被直接赋予了哪些 NTFS 权限,请注意“isInherited”属性。这段代码将创建一个名为“_sampleFolderNTFS_”的新文件夹,并且列出所有非继承的 NTFS 权限。当您创建该文件夹时,它只拥有继承的权限,所以您查看非继承权限的时候获得不到任何结果:

$Path = 'c:\sampleFolderNTFS'

# create new folder
$null = New-Item -Path $Path -ItemType Directory -ErrorAction SilentlyContinue

# get permissions
$acl = Get-Acl -Path $path
$acl.Access |
  Where-Object { $_.isInherited -eq $false }

当您增加了非继承权限时,这段代码将会产生结果。这是通过 PowerShell 增加非继承权限的方法。它将针对当前用户添加读、写、修改权限:

$acl = Get-Acl -Path $path

# add a new permission
$permission = $env:username, 'Read,Write,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)

# set new permissions
$acl | Set-Acl -Path $path

PowerShell 技能连载 - 管理 NTFS 权限

在前一个技能中我们演示了如何向一个文件夹增加 NTFS 权限。要查看可以设置哪些权限,看以下示例:

PS> [System.Enum]::GetNames([System.Security.AccessControl.FileSystemRights])
ListDirectory
ReadData
WriteData
CreateFiles
CreateDirectories
AppendData
ReadExtendedAttributes
WriteExtendedAttributes
Traverse
ExecuteFile
DeleteSubdirectoriesAndFiles
ReadAttributes
WriteAttributes
Write
Delete
ReadPermissions
Read
ReadAndExecute
Modify
ChangePermissions
TakeOwnership
Synchronize
FullControl

假设您已创建了一个名为“_protectedfolder_”的文件夹:

$Path = 'c:\protectedFolder'

# create new folder
$null = New-Item -Path $Path -ItemType Directory

要为“_Tobias_”用户(请将用户名替换为您系统中实际存在的用户名)增加文件系统权限,请运行这段代码:

# get permissions
$acl = Get-Acl -Path $path

# add a new permission
$permission = 'Tobias', 'Read,Write,Modify', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)

# set new permissions
$acl | Set-Acl -Path $path

PowerShell 技能连载 - 创建一个包含 NTFS 权限的文件夹

您常常会需要创建一个文件夹并且为它设置 NTFS 权限。

这是一个简单的创建新文件夹并演示如何向已有权限中增加新的权限的示例代码:

$Path = 'c:\protectedFolder'

# create new folder
$null = New-Item -Path $Path -ItemType Directory

# get permissions
$acl = Get-Acl -Path $path

# add a new permission
$permission = 'Everyone', 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)

# add another new permission
# WARNING: Replace username "Tobias" with the user name or group that you want to grant permissions
$permission = 'Tobias', 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow'
$rule = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $permission
$acl.SetAccessRule($rule)

# set new permissions
$acl | Set-Acl -Path $path

PowerShell 技能连载 - 分割超长代码行

为了提高可读性,您可以将超长的 PowerShell 代码行拆分成多行。

Get-Service | Where-Object { $_.Status -eq 'Running' }

Get-Service |
  Where-Object { $_.Status -eq 'Running' }

在管道符之后,您可以加入一个换行。您也可以安全地在一个左大括号之后、右大括号之前加入一个换行:

Get-Service |
  Where-Object {
    $_.Status -eq 'Running'
  }

如果您需要在其它地方换行,那么请在换行之前加入一个反引号:

Get-Service |
Where-Object `
{
  $_.Status -eq 'Running'
}

PowerShell 技能连载 - 显示函数参数

通过一个简单的技巧,您可以创建一个对话框窗口来帮助用户提供您写的函数的所需要参数。

只需要使用 $PSBoundParameters 来判定用户是否传入了参数。如果用户未传入参数,那么请运行 Show-Command 并传入您的函数名,然后返回您的函数,其它什么也不用做。

Show-Command 自动解决剩下的部分:它会显示一个包括所有函数参数的对话框,当用户点击“运行”时,它将以提交的参数运行该函数。

function Show-PromptInfo
{
  param
  (
    [string]
    $Name,

    [int]
    $ID
  )
  if ($PSBoundParameters.Count -eq 0)
  {
    Show-Command -Name Show-PromptInfo
    return
  }

  "Your name is $name, and the id is $id."
}

当您执行 Show-PromptInfo 函数时传入了正确的参数,则将立即执行该函数的内容。

PS> Show-PromptInfo -Name weltner -ID 12
Your name is weltner, and the id is 12.

PS> Show-PromptInfo
<# Dialog opens, then runs the function with submitted parameters#>

当您执行该函数时没有传入任何参数,则将弹出一个对话框,提示您交互式地输入参数。

PowerShell 技能连载 - 使用 PowerShell 的帮助窗口作为通用输出

要显示文本信息,您当然可以启动 noteapd.exe,并且用编辑器来显示文本。不过,在编辑器中显示文本并不是一个好主意,如果您不希望文本被改变。

PowerShell 给我们带来了一个很棒的窗体,用来显示一小段或者中等长度的文本:内置的帮助窗口。

通过一些调整,该窗口可以重新编程,显示任意的文本信息。而且,您可以使用内置的全文搜索功能在您的文本中导航,甚至可以根据你的喜好设置前景色和背景色。

只需要将任意的文本通过管道传递给 Out-Window,并且根据需要可选地指定颜色、搜索词和标题。以下是 Out-Window 函数:

function Out-Window
{
  param
  (
    [String]
    $Title = 'PowerShell Output',

    [String]
    $FindText = '',

    [String]
    $ForegroundColor = 'Black',

    [String]
    $BackgroundColor = 'White'
  )

  # take all pipeline input:
  $allData = @($Input)

  if ($allData.Count -gt 0)
  {
    # open window in new thread to keep PS responsive
    $code = {
      param($textToDisplay, $FindText, $Title, $ForegroundColor, $BackgroundColor)

      $dialog = (New-Object –TypeName Microsoft.Management.UI.HelpWindow($textToDisplay))
      $dialog.Title = $Title
      $type = $dialog.GetType()
      $field = $type.GetField('Settings', 'NonPublic,Instance')
      $button = $field.GetValue($dialog)
      $button.Visibility = 'Collapsed'
      $dialog.Show()
      $dialog.Hide()
      $field = $type.GetField('Find', 'NonPublic,Instance')
      $textbox = $field.GetValue($dialog)
      $textbox.Text = $FindText
      $field = $type.GetField('HelpText', 'NonPublic,Instance')
      $RTB = $field.GetValue($dialog)
      $RTB.Background = $BackgroundColor
      $RTB.Foreground = $ForegroundColor
      $method = $type.GetMethod('MoveToNextMatch', [System.Reflection.BindingFlags]'NonPublic,Instance')
      $method.Invoke($dialog, @($true))
      $dialog.ShowDialog()
    }

    $ps = [PowerShell]::Create()
    $newRunspace = [RunSpaceFactory]::CreateRunspace()
    $newRunspace.ApartmentState = 'STA'
    $newRunspace.Open()

    $ps.Runspace = $newRunspace
    $null = $ps.AddScript($code).AddArgument(($allData | Format-Table -AutoSize -Wrap | Out-String -Width 100)).AddArgument($FindText).AddArgument($Title).AddArgument($ForegroundColor).AddArgument($BackgroundColor)
    $null = $ps.BeginInvoke()
  }
}

调用的示例如下:

Get-Content C:\Windows\windowsupdate.log  |
  # limit to first 100 lines (help window is not designed to work with huge texts)
  Select-Object -First 100 |
  Out-Window -Find Success -Title 'My Output' -Background Blue -Foreground White

请记住两件事:

  • 帮助窗口并不是设计为显示大量文本的。请确保使用该方法显示不超过几 KB 的文本。
  • 这只是试验性的代码。它并没有清除 PowerShell 用来显示窗体所创建的线程。当您关闭该线程时,该 PowerShell 线程将保持在后台运行,直到您关闭 PowerShell。我们需要为帮助窗口关闭事件增加一个事件处理器。该事件处理器可以清理该 PowerShell 线程。

PowerShell 技能连载 - 在后台播放声音

如果您的脚本执行起来需要较长时间,您可能会希望播放一段系统声音文件。以下是一个实现该功能的示例代码:

# find first available WAV file in Windows
$WAVPath = Get-ChildItem -Path $env:windir -Filter *.wav -Recurse -ErrorAction SilentlyContinue |
Select-Object -First 1 -ExpandProperty FullName

# load file and play it
$player = New-Object Media.SoundPlayer $WAVPath

try
{
  $player.PlayLooping()
  'Doing something...'

  1..100 | ForEach-Object {
    Write-Progress -Activity 'Doing Something. Hang in' -Status $_ -PercentComplete $_
    Start-Sleep -MilliSeconds (Get-Random -Minimum 300 -Maximum 1300)
  }
}

finally
{
  $player.Stop()
}

这段示例代码使用 Windows 文件夹中找到的第一个 WAV 文件,然后在脚本的执行期间播放它。您当然也可以指定其它 WAV 文件的路径。