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 文件的路径。

PowerShell 技能连载 - 查找可执行程序

许多文件扩展名都被关联为可执行程序。您可以使用 Invoke-Item 来打开一个可执行的文件。

然而,查找哪些文件扩展名是可执行程序却不是那么简单。您可以读取 Windows 注册表,然后自己查找这些值。如果您采用这种方法,请注意 32/64 位的问题。

另外一个方法是使用 Windows API。以下示例代码演示了它是如何工作的。如果您使用了这种方法,您可以将重活交给操作系统做。付出的代价是一堆调用内部 API 函数的 C# 代码。

$Source = @"

using System;
using System.Text;
using System.Runtime.InteropServices;
public class Win32API
    {
        [DllImport("shell32.dll", EntryPoint="FindExecutable")]

        public static extern long FindExecutableA(string lpFile, string lpDirectory, StringBuilder lpResult);

        public static string FindExecutable(string pv_strFilename)
        {
            StringBuilder objResultBuffer = new StringBuilder(1024);
            long lngResult = 0;

            lngResult = FindExecutableA(pv_strFilename, string.Empty, objResultBuffer);

            if(lngResult >= 32)
            {
                return objResultBuffer.ToString();
            }

            return string.Format("Error: ({0})", lngResult);
        }
    }

"@

Add-Type -TypeDefinition $Source -ErrorAction SilentlyContinue

$FullName = 'c:\Windows\windowsupdate.log'
$Executable = [Win32API]::FindExecutable($FullName)

"$FullName will be launched by $Executable"

一个已知的限制是 FindExecutable() 的使用前提是该文件必须存在。您无法只通过文件扩展名来断定是否为一个可执行文件。

PowerShell 技能连载 - 根据大写字符分割文本

要在一段文本的每个大写字符出分割这段文本,而不用提供一个大写字符的列表,请试试这个例子:

$text = 'MapNetworkDriveWithCredential'

[Char[]]$raw = foreach ($character in $text.ToCharArray())
{
  if ([Char]::IsUpper($character))
  {
    ' '
  }
  $character
}

$newtext = (-join $raw).Trim()
$newtext

PowerShell 技能连载 - 查找大写字符

如果您希望查找大写字符,那么可以使用正则表达式。然而,您也可以提供一个大写字符的列表作为对比使用。一个更灵活的办法是使用 .NET 的 IsUpper() 函数。

以下是一段示例代码:它逐字符扫描一段文本,然后返回首个大写字符的位置:

$text = 'here is some text with Uppercase letters'

$c = 0
$position = foreach ($character in $text.ToCharArray())
{
  $c++
  if ([Char]::IsUpper($character))
  {
    $c
    break
  }
}

if ($position -eq $null)
{
  'No uppercase characters detected.'
}
else
{
  "First uppercase character at position $position"
  $text.Substring(0, $position) + "<<<" + $text.Substring($position)
}

执行的结果类似这样:

PS C:\>

First uppercase character at position 24
here is some text with U<<<ppercase letters