设为首页 收藏本站
查看: 1017|回复: 0

27. PowerShell-- 文件系统(2)

[复制链接]

尚未签到

发表于 2018-9-2 11:43:06 | 显示全部楼层 |阅读模式

  •   PowerShell之 文件系统:使用目录和文件工作
  Get-ChildItem 和 Get-Item 命令可以获取已经存在的文件和目录。你也可以创建自己的文件和目录,重命名它们,给它们填充内容,复制它们,移动它们,当然也可以删除它们。
创建新目录
  创建一个新目录最方便的方式是使用MD函数,它内部调用的是New-Item命令,指定参数–type的值为“Directory”:
# "md"是一个内置的函数用来创建新目录  
PS D:\\> md Test1
  

  
    目录: D:\\
  

  
Mode                LastWriteTime     Length Name
  
----                -------------     ------ ----
  
d----         2014/1/23     18:13            Test1
  

  
# "New-Item",也可以做这些,但是得多花点功夫
  
PS D:\\> New-Item Test2 -type Directory
  

  
    目录: D:\\
  

  
Mode                LastWriteTime     Length Name
  
----                -------------     ------ ----
  
d----         2014/1/23     18:13            Test2
  注意:你也可以一次性创建多层子目录,如果你指定的目录不存在,PowerShell会自动创建这些目录:
  1
  md test\\subdirectory\\somethingelse
  只要test和Subdirectory目录都不存在,就会创建三个子目录。
创建新文件
  可能之前你已经使用过New-Item来创建过文件,但是它们完全是空的:
PS> New-Item "new file.txt" -type File  

  
    目录: D:\\
  

  
Mode                LastWriteTime     Length Name
  
----                -------------     ------ ----
  
-a---         2014/1/23     19:14          0 new file.txt
  文件通常会在你保存数据时,自动被创建。因为空文件一般没多大用处。此时重定向和Out-File,Set-Content这两个命令可以帮助你:
Dir > info1.txt  
.\\info1.txt
  
Dir | Out-File info2.txt
  
.\\info2.txt
  
Dir | Set-Content info3.txt
  
.\\info3.txt
  
Set-Content info4.txt (Get-Date)
  
.\\info4.txt
  事实证明在操作上重定向Out-File非常的类似:当PowerShell转换管道结果时,文件的内容就像它在控制台上面输出的一样。Set-Content呢,稍微有所不同。它在文件中只列出目录中文件的名称列表,因为在你使用Set-Content时,PowerShell不会自动将对象转换成文本输入。相反,Set-Content会从对象中抽出一个标准属性。上面的情况下,这个属性就是Name了。
  通常,你可以将任何文本写入一个文本文件。最后一行演示的是将一个日期对象写入到文件中。比如你手动使用ConvertTo-HTML将管道结果转换后,Out-FileSet-Content会殊途同归。
  1
  2
  3
  4
  Dir | ConvertTo-HTML | Out-File report1.htm
  .\\report1.htm
  Dir | ConvertTo-HTML | Set-Content report2.htm
  .\\report2.htm
  如果你想决定对象的那个属性应当显示在HTML页面中,可以使用第5章中提到的Select-Object 在对象转换成HTML前过滤属性
  1
  2
  3
  Dir | Select-Object name, length, LastWriteTime |
  ConvertTo-HTML | Out-File report.htm
  .\\report.htm
  在重定向的过程中,控制台的编码会自动指定特殊字符在文本中应当如何显示。你也可以在使用Out-File命令时,使用-encoding参数来指定。
  如果你想将结果导出为逗号分割符列表,可以使用Export-CSV代替Out-File
  你可以使用双重定向Add-Content向一个文本文件中追加信息。
Set-Content info.txt "First line"  
"Second line" >> info.txt
  
Add-Content info.txt "Third line"
  
Get-Content info.txt
  
First Line
  
S e c o n d L i n e
  
Third line
  这个结果让小伙伴们惊呆了:双箭头重定向可以工作,但是文本中显示的字符有间隔。重定向操作符通常使用的是控制台的字符集,如果你的文本中碰巧同时包含了ANSI和Unicode字符集,可能会引起意外的结果。相反,使用Set-ContentAdd-ContentOut-File这几条命令,而不使用重定向,可以有效地规避前面的风险。这三条命令都支持-encoding参数,你可以用它来选择字符集。
创建新驱动器
  你可能会惊讶,PowerShell允许你创建新的驱动器。并且不会限制你只创建基于网络的驱动器。你还可以使用驱动器作为你的文件系统中重要目录,甚至你自定义的文件文件系统的一个方便的快捷方式。
  使用New-PSDrive命令来创建一个新的驱动器。可以像下面那样创建一个网络驱动器。
PS> New-PSDrive -name network -psProvider FileSystem -root \\\\127.0.0.1\\share  

  
Name           Used (GB)     Free (GB) Provider      Root                                               CurrentLocation
  
----           ---------     --------- --------      ----                                               ---------------
  
network                                FileSystem    \\\\127.0.0.1\\share
  

  
PS> dir network:
  

  
    目录: \\\\127.0.0.1\\share
  

  
Mode                LastWriteTime     Length Name
  
----                -------------     ------ ----
  
d----          2013/3/1     14:12            ALwaysOn
  
d----          2013/1/4     10:32            Doc
  
d----         2013/8/26     13:20            Driver
  
-a---         2013/7/10     14:18    7983059 Models.zip
  
-a---          2013/5/7     20:44        616 SelectManualCase.ps1
  
-a---        2006/10/14     10:18     428832 UISpy.exe
  
-a---         2013/3/13     12:23      19435 WMSCloudTopology.xml
  在工作目录中创建一个快捷方式也非常方便。下面的命令行会创建一个名为desktop: 和 docs:的驱动器,它可以代表你的”桌面“目录和Windows目录:“我的文档”。
  1
  2
  3
  4
  New-PSDrive desktop FileSystem `
  ([Environment]::GetFolderPath("Desktop")) | out-null
  New-PSDrive docs FileSystem `
  ([Environment]::GetFolderPath("MyDocuments")) | out-null
  然后你想更改当前目录为桌面时,只须输入:
  1
  Cd desktop:
  使用Remove-PSDrive来删除你创建的驱动器。如果该驱动器正在使用则不能删除。注意在使用New-PSDriveRemove-PSDrive创建或删除驱动器时,指定的字母不能包含冒号,但是在使用驱动器工作时必须指定冒号。
  1
  Remove-PSDrive desktop
读取文本文件的内容
  使用Get-Content可以获取文本文件的内容:
  1
  Get-Content $env:windir\\windowsupdate.log
  如果你知道文件的绝对路径,还可以使用变量符号这个快捷方式读取文本内容:
  1
  ${c:\\windows\\windowsupdate.log}
  通常,这个符号不是很实用,因为在括号中不允许适用任何变量。而大多数情况下绝对路径不会适用所有机器的操作系统。
  Get-Content 逐行读取文本的内容,然后把文本的每一行传递给管道。因此,在你想读取一个长文件的前10行,应当适用Select-Object:
  1
  Get-Content $env:windir\\windowsupdate.log | Select-Object -first 10
  使用Select-String可以过滤出文本文件中的信息。下面的命令行会从windowsupdate.log文件中过滤出包含”added update”短语的行。
  1
  Get-Content $env:windir\\windowsupdate.log | Select-String "Added update"
处理逗号分隔的列表
  在PowerShell中处理逗号分隔的列表文件中的信息时你须要使用Import-Csv文件。为了测试,先创建一个逗号分隔的文本文件。
Set-Content user.txt "Username,Function,Passwordage"  
Add-Content user.txt "Tobias,Normal,10"
  
Add-Content user.txt "Martina,Normal,15"
  
Add-Content user.txt "Cofi,Administrator,-1"
  
Get-Content user.txt
  
Username,Function,Passwordage
  
Tobias,Normal,10
  
Martina,Normal,15
  
Cofi,Administrator,-1
  然后就可以使用Import-Csv输入列表文件了,
PS> Import-Csv user.txt  

  
Username Function      Passwordage
  
-------- --------      -----------
  
Tobias   Normal        10
  
Martina  Normal        15
  
Cofi     Administrator -1
  如你所见,Import-Csv理解逗号文件的格式,并且可以逐列显示数据。所以在解析逗号分割的文本文件时,你可以节省下很多工作量:Import-Csv会替你完成。第一行被解析成列的标题。然后你就可以将非常方便地将逗号分隔的值作为输入,比如创建用户账号。
Import-Csv user.txt | ForEach-Object { $_.Username }  
Tobias
  
Martina
  
Cofi
  高级主题:除了使用ForEach-Object循环你还可以在括号中使用脚本块。对于每一个管道内部的管道对象,脚本块都会被执行。在下面的例子中,逗号分割文件中的每一个用户名都会通过echo的参数-InputObject返回并输出。
Import-Csv user.txt | echo -InputObject {$_.Username }
解析文本内容和提取文本信息
  经常会碰到的一个任务就是解析原始数据,比如日志文件,从所有的数据中获取结构化的目标信息。比如日志文件:windowsupdate.log 它记录了windows更新的细节信息(在之前的例子中我们已经多次用到过这个小白鼠)。该文件还有大量数据,以至于乍一看没什么可读性。初步分析表明该文件是逐行存储的信息,并且每行的信息片段是以Tab字符分割的。
  正则表达式为描述这类文件格式提供了最方便的方式,之前在第13章已经提到过。你可以按照下面的例子来使用正则表达式适当地描述文件indowsupdate.log的内容。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  # 文本模式包含了6个Tab字符分割的数组
  $pattern = "(.*)\\t(.*)\\t(.*)\\t(.*)\\t(.*)\\t(.*)"
  # 输入日志
  $text = Get-Content $env:windir\\windowsupdate.log
  # 从日志文件中提取出任意行(这里是第21行)
  $text[20] -match $pattern
  True
  $matches
  Name Value
  ---- -----
  6 * Added update {17A5424C-4C70-4BB4-8F83-66DABE5E7CA2}.201 to search result
  5 Agent
  4 19a4
  3 448
  2 11:30:42:237
  1 2014-02-10
  0 2014-02-10 11:30:42:237 448 19a4 Agent * Added update {17A5424C-4C70-4BB4-8F83-66DABE5E7CA2}....
  $matches返回了每个圆括号中定义的子正则表达式的匹配项,这样你就可以使用数字索引来寻址每个文本数组元素了。比如你只对某一行中的日期和描述感兴趣,然后格式化输出它:
PS > "On {0} this took place: {1}" -f $matches[1], $matches[6]  
On 2014-02-10 this took place:   * Added update {17A5424C-4C70-4BB4-8F83-66DABE5E7CA2}.201 to search result
  这种情况下,推荐给每一个子表达式取一个名字,这样可以在后面通过该名字访问。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  # 这次子表达式拥有一个名称:
  $pattern = "(?.*)\\t(?.*)\\t(?.*)" + "\\t(?.*)\\t(?.*)\\t(?.*)"
  # 输入日志:
  $text = Get-Content $env:windir\\windowsupdate.log
  # 从日志中提取任意行来解析(这里取第21行):
  $text[20] -match $pattern
  True
  # 从 $matches 中获取信息
  # 可以访问指定的名称:
  $matches.time + $matches.text
  11:30:42:237 * Added update {17A5424C-4C70-4BB4-8F83-66DABE5E7CA2}.201 to search result
  现在你可以使用Get-Content一行一行读取整个日志文件了,然后使用上面的方式逐行处理。这意味着即使在一个庞大的文件中,你也可以快速,相对高效地收集所有你需要的信息。下面的例子正好会列出那些日志行的描述信息中包含了短语“woken up”的文本行。这可以帮助你找出一台机器是否曾经因为自动更新被从待机或者休眠模式唤醒。
Get-Content $env:windir\\windowsupdate.log |  
ForEach-Object { if ($_ -match "woken up") { $_ } }
  

  
2013-05-24 03:00:34:609 1276 1490 AU The machine was woken up by Windows Update
  
2013-05-24 03:00:34:609 1276 1490 AU The system was woken up by Windows Update, but found to be running on battery power. Skip the forcedinstall.
  
2013-06-28 03:00:11:563 1272 fe0 AU The machine was woken up by Windows Update
  如果进入循环,会将保存在$_中的完整文本行输出。你现在知道了如何使用正则表达式将一个包含特定信息片段的文本行分割成数组。
  然而,还有第二种,更为精妙的方法,从文件中选择个别文本行,它就是Switch。你只需要告诉语句块,那个文件你想检查,那个模式你想匹配。剩下的工作就交给Switch吧!下面的语句会获取所有安装的自动更新日志。使用它比之前使用的Get-ContentForEach-Object更快速你只需要记住正则表达式“.*”代表任意数量的任意字符。
Switch -regex -file $env:windir\\wu1.log {  'START.*Agent: Install.*AutomaticUpdates' { $_ }}
  

  
2013-05-19 09:22:04:113 1248 1d0c Agent **START**
  
Agent: Installing updates [CallerId = AutomaticUpdates]
  
2013-05-24 22:31:51:046 1276 c38 Agent **START**
  
Agent: Installing updates [CallerId = AutomaticUpdates]
  
2013-06-13 12:05:44:366 1252 228c Agent **START**
  
Agent: Installing updates [CallerId = AutomaticUpdates]
  如果你想找到其它程序的更新,比如SMS或者Defender。只需要在你的正则表达式中使用“SMS”或者“Defender”替换“automatic updates”即可。事实上,Switch可以接受多个模式,按照下面声明在花括号中的那样,依赖多个模式进行匹配。这就意味着只需几行代码,就可以找出多个程序的更新。
# 为结果创建一个哈希表:  
result = @{Defender=0; AutoUpdate=0; SMS=0}
  
# 解析更新日志,并将结果保存在哈希表中:
  
Switch -regex -file $env:windir\\wu1.log
  
{
  
'START.*Agent: Install.*Defender' { $result.Defender += 1 };
  
'START.*Agent: Install.*AutomaticUpdates' { $result.AutoUpdate +=1 };
  
'START.*Agent: Install.*SMS' { $result.SMS += 1}
  
}
  

  
# 输出结果:
  
$result
  

  
NameValue
  
---------
  
SMS0
  
Defender1
  
AutoUpdate 8
读取二进制的内容
  不是所有的文件都包含文本。有时,我们需要读取二进制文件中的信息。正常情况下一个文件的扩展名扮演的很重要的角色。因为它决定了Windows使用什么程序来打开这个文件。然而在许多二进制文件中,文件头也紧密的集成到文件中。这些文件头包含了该文件是属于那一类文件的内部类型名称。借助于参数-readCount和-totalCount,Get-Content可以获取这些“魔法字节”。参数-readCount指明每次读取多少字节,-totalCount决定了你想从文件中读取的总的字节数。当前情况下,你需要从文件中读取的应当是前4个字节。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  function Get-MagicNumber ($path)
  {
  Resolve-Path $path | ForEach-Object {
  $magicnumber = Get-Content -encoding byte $_ -read 4 -total 4
  $hex1 = ("{0:x}" -f ($magicnumber[0] * `
  256 + $magicnumber[1])).PadLeft(4, "0")
  $hex2 = ("{0:x}" -f ($magicnumber[2] * `
  256 + $magicnumber[3])).PadLeft(4, "0")
  [string] $chars = $magicnumber| %{ if ([char]::IsLetterOrDigit($_))
  { [char] $_ } else { "." }}
  "{0} {1} '{2}'" -f $hex1, $hex2, $chars
  }
  }
Get-MagicNumber "$env:windir\\explorer.exe"  
4d5a 9000 'M Z . .'
  Explorer的前四个字节为4d, 5a, 90, 和 00或者已经列出的文本MZ。这是Microsoft DOS的开发者之一Mark Zbikowski的简称。所以,标记MZ就代表了可执行的程序。这个标记和图片文件的标记不同:
PS> Get-MagicNumber "$env:windir\\Web\\Wallpaper\\Scenes\\*"  
ffd8 ffe0 '   à'
  
ffd8 ffe0 '   à'
  
ffd8 ffe0 '   à'
  
ffd8 ffe0 '   à'
  
ffd8 ffe0 '   à'
  
ffd8 ffe0 '   à'
  如你所见,Get-Content也可以读取二进制文件,一次只读一个字节。参数-readCount指定每一步读取多少个字节。-totalCount指定总共要读取的字节数,一旦给它赋值为-1,它会从头到尾读取所有文件内容。你可以通过将数据输出为十六进制来预览可执行文件。因为纯二进制文本不易阅读。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  function Get-HexDump($path,$width=10, $bytes=-1)
  {
  $OFS=""
  Get-Content -encoding byte $path -readCount $width -totalCount $bytes | ForEach-Object {
  $characters = $_
  if (($characters -eq 0).count -ne $width)
  {
  $hex = $characters | ForEach-Object {
  " " + ("{0:x}" -f $_).PadLeft(2,"0")}
  $char = $characters | ForEach-Object {
  if ([char]::IsLetterOrDigit($_))
  { [char] $_ } else { "." }}
  "$hex $char"
  }
  }
  }
PS> Get-HexDump $env:windir\\explorer.exe -width 15 -bytes 150  
4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 MZ...........
  
00 b8 00 00 00 00 00 00 00 40 00 00 00 00 00 ...............
  
e0 00 00 00 0e 1f ba 0e 00 b4 09 cd 21 b8 01 à............
  
4c cd 21 54 68 69 73 20 70 72 6f 67 72 61 6d L.This.program
  
20 63 61 6e 6e 6f 74 20 62 65 20 72 75 6e 20 .cannot.be.run.
  
69 6e 20 44 4f 53 20 6d 6f 64 65 2e 0d 0d 0a in.DOS.mode....
  
24 00 00 00 00 00 00 00 93 83 28 37 d7 e2 46 ...........7.F
  
64 d7 e2 46 64 d7 e2 46 64 de 9a c2 64 9d e2 d.Fd.Fd.d.
移动和复制文件和目录
  Move-Item 和 Copy-Item用来执行移动和拷贝操作。它们也支持通配符。比如下面的脚本会将你家目录下的的所有PowerShell脚本文件复制到桌面上:
  1
  Copy-Item $home\\*.ps1 ([Environment]::GetFolderPath("Desktop"))
  但是,只有在家目录当下的脚本会被复制。幸亏Copy-Item还有一个参数-recurse,这个参数的效果类似Dir中的效果。如果你的初始化目录不包含任何目录,它也不会工作。
  1
  Copy-Item -recurse $home\\*.ps1 ([Environment]::GetFolderPath("Desktop"))
  使用Dir也可以复制所有PowerShell脚本到你的桌面,让我们先给你找出这些脚本,然后将结果传递给Copy-Item:
  1
  2
  Dir -filter *.ps1 -recurse | ForEach-Object {
  Copy-Item $_.FullName ([Environment]::GetFolderPath("Desktop")) }
  小技巧:你可能被诱惑去缩减脚本行,因为文件对象整合了一个CopyTo()方法。
  1
  2
  Dir -filter *.ps1 -recurse | ForEach-Object {
  $_.CopyTo([Environment]::GetFolderPath("Desktop")) }
  但是结果可能会出错,因为CopyTo()是一个低级的函数。它需要文件的目标路径也被复制。因为你只是想复制所有文件到桌面,你已经指定了目标路径的目录。CopyTo()会尝试将文件复制这个精确的字符串路径(桌面)下,但是肯定不会得逞,因为桌面是一个已经存在的目录了。相反的Copy-Item就聪明多了:如果目标路径是一个目录,它就会把文件复制到这个目录下。
  此时,你的桌面上可能已经堆满了PowerShell脚本,最好的方式是将它们保存到桌面的一个子目录中。你需要在桌面上创建一个新目录,然后从桌面到这个子目录中移动所有的脚本。
  1
  2
  3
  $desktop = [Environment]::GetFolderPath("Desktop")
  md ($desktop + "\\PS Scripts")
  Move-Item ($desktop + "\\*.ps1") ($desktop + "\\PS Scripts")
  此时,你的桌面又恢复了往日的整洁,也把脚本安全的保存到桌面了。
重命名文件和目录
  使用Rename-Item你可以给文件或者目录换个名字。但是这样做时要格外小心,因为如果把某些系统文件给重命名了,可能会导致系统瘫痪。甚至你只是更改了某些文件的扩展名,也会导致它们不能正常打开或者显示它们的一些属性。
  1
  2
  3
  4
  5
  6
  Set-Content testfile.txt "Hello,this,is,an,enumeration"
  # 在默认编辑器中打开文件:
  .\\testfile.txt
  # 在Excel中打开文件:
  Rename-Item testfile.txt testfile.csv
  .\\testfile.csv
批量重命名
  因为Rename-Item可以在管道中的语句块中使用,这就给一些复杂的任务提供了令人惊讶的方便的解决方案。比如,你想将一个目录的名称和它的子目录的名称,包括目录下的文件的名称中所有的“x86”词语移除掉。下面的命令就够了:
  1
  2
  Dir | ForEach-Object {
  Rename-Item $_.Name $_.Name.replace("-x86", "") }
  然而,上面的命令会实际上会尝试重命名所有的文件和目录,即使你找的这个词语在文件名中不存在。产生错误并且非常耗时。为了大大提高速度,可是使用Where-Object先对文件名进行过滤,然后对符合条件的文件进行重命名,可以将速度增长50倍:(荔非苔注:为什么是50倍呢?我不知道。)
  1
  2
  Dir | Where-Object { $_.Name -contains "-x86" } | ForEach-Object {
  Rename-Item $_.Name $_.Name.replace("-x86", "") }
更改文件扩展名
  如果你想更改文件的扩展名,首先需要意识到后果:文件随后会识别为其它文件类型,而且可能被错误的应用程序打开,甚至不能被任何应用程序打开。下面的命令会把当前文件夹下的所有的PowerShell脚本的后缀名从“.ps1”改为“.bak”。
  1
  2
  3
  4
  5
  6
  Dir *.ps1 | ForEach-Object { Rename-Item $_.Name `
  ([System.IO.Path]::GetFileNameWithoutExtension($_.FullName) + `
  ".bak") -whatIf }
  What if: Performing operation "Rename file" on Target
  "Element: C:\\Users\\Tobias Weltner\\tabexpansion.ps1
  Destination: C:\\Users\\Tobias Weltner\\tabexpansion.bak".
  由于-whatIf参数的缘故,一开始语句只会表明可能会执行重命名操作。
整理文件名
  数据集往往随着时间的增长而增长。如果你想整理一个目录,你可以给定所有的文件一个统一的名称和序号。你可以从文件的某些具体的属性中合成文件名。还记得上面在桌面上为PowerShell脚本创建的那个子目录吗?让我们对它里面的PowerShell脚本以数字序号重命名吧。
  1
  2
  3
  Dir $directory\\*.ps1 | ForEach-Object {$x=0} {
  Rename-Item $_ ("Script " + $x + ".ps1"); $x++ } {"Finished!"}
  Dir $directory\\*.ps1
删除文件和目录
  使用Remove-Item和别名Del可以删除文件和目录,它会不可恢复的删除文件和目录。如果一个文件属于只读文件,你需要指定参数-force :
# 创建示例文件:  
$file = New-Item testfile.txt -type file
  
# 文件不是只读:
  
$file.isReadOnly
  
False
  
# 激活只读属性:
  
$file.isReadOnly = $true
  
$file.isReadOnly
  
True
  
# 只读的文件需要指定-Force参数才能顺利删除:
  
del testfile.txt
  
Remove-Item : Cannot remove item C:\\Users\\Tobias Weltner\\testfile.txt: Not enough permission to perform operation.
  
At line:1 char:4
  
+ del  (Get-Acl $env:windir).Access | Format-Table -wrap  

  
   FileSystemRights   AccessControlType IdentityReference           IsInherited    InheritanceFlags    PropagationFlags
  
   ----------------   ----------------- -----------------           -----------    ----------------    ----------------
  
          268435456               Allow CREATOR OWNER                     False  ContainerInherit,          InheritOnly
  
                                                                                      ObjectInherit
  
          268435456               Allow NT AUTHORITY\\SYSTEM               False  ContainerInherit,          InheritOnly
  
                                                                                      ObjectInherit
  
Modify, Synchronize               Allow NT AUTHORITY\\SYSTEM               False                None                None
  
          268435456               Allow BUILTIN\\Administrat               False  ContainerInherit,          InheritOnly
  
                                        ors                                           ObjectInherit
  
Modify, Synchronize               Allow BUILTIN\\Administrat               False                None                None
  
                                        ors
  
        -1610612736               Allow BUILTIN\\Users                     False  ContainerInherit,          InheritOnly
  
                                                                                      ObjectInherit
  
   ReadAndExecute,                Allow BUILTIN\\Users                     False                None                None
  
        Synchronize
  
          268435456               Allow NT SERVICE\\TrustedI               False    ContainerInherit         InheritOnly
  
                                        nstaller
  
        FullControl               Allow NT SERVICE\\TrustedI               False                None                None
  
                                        nstaller
  在上面表格的IdentityReference列,告诉你谁有特殊的权限。FileSystemRights列告诉你权限的类型。AccessControlType列格外重要,如果它显示“拒绝”而不是“允许”,你懂的,它会限制用户访问。
创建新的权限
  Get-Acl执行后返回的对象,包含若干方法可以用来更新权限和设定所有权。如果你只想设定自己的权限,都没必要去安全描述符世界深究。往往,读取一个已经存在的文件安全描述符,把它传递给另一个文件,或者按照特殊SDDL语言文字的形式指定安全信息就够了。
  技巧:下面的例子会让你认识一些日常步骤。注意两点即可:别忘了cacls这个可靠的工具,因为使用它会比PowerShell命令更高效。此外,Get-ACL和Set-ACL不仅仅应用于文件层面,还可以用于其它有访问控制的安全描述符的任何地方,比如Windows注册表(会在下一章讲解)。
克隆权限
  在一个初级的案例中,你可能都不会创建任何新的权限,只会从一个已经存在的文件或者目录的访问控制列表中克隆一个权限,然后把它转让给其它文件。优点是可以使用图形用户界面来设置那些通常比较复杂的权限。
  注意:因为手动调整安全设置是一项专业,各个Windows系统不通用(像Windows XP Home就没有这个选项)的工作。尽管如此,你却可以使用PowerShell在不同的Windows版本中设置权限。
  开始之前,先创建两个目录作为测试:
  1
  2
  md Prototype | out-null
  md Protected | out-null
  现在,打开资源管理器,设置Prototype目录的安全设置。
  1
  explorer .
  在资源管理器中,右击Prototype目录,选择属性,然后点击安全选项卡,点击编辑(win7和win8中)。通过添加其他用户来更改测试目录的安全设置。在下面的对话框中给新用户设置权限。
  注意:你也可以通过勾选拒绝复选框来拒绝用户的权限。这样做时,可要留心了。因为限制权限总是有高优先级。比如,你给了自己完全控制的权限,但是拒绝了“Everyone”这个组来访问。这样就把自己关在文件系统的外面了。因为你也属于”Everyone”这个组,同时因为限制的优先级比较高,哪怕你已经给了自己“完全控制”的权限,这个限制也作用于你。
  你更改了权限后,捎带在资源管理器中看看第二个目录Protected。这个目录仍旧是默认赋予的权限。下一步,我们会把Prototype刚才设置的权限转交到Protected目录。
  1
  2
  $acl = Get-Acl Prototype
  Set-Acl Protected $acl
  注意:你本身需要特殊的权限去设置上面的权限。如果你用的是Windows Vista操作系统,并且启用了UAC,使用PowerShell操作时,会出现错误,提示你没有权限。这时可以通过让控制台以管理员权限运行来获取权限。
  实验做完了,现在呢,Protected和Prototype一样安全。当你在资源管理器中查看它们的安全设置时,你会发现所有的设置都是相同的。
使用SDDL设置权限
  前面的例子非常简单,你所做的只是把已有目录的安全设置移交给其它目录。在你的日常工作中,你可能得具备一个你根本就不需要的Prototype目录。但是你可以通过文本格式的安全描述符来归纳安全设置。每一个安全设置都是被特殊的安全描述符描述语言(SDDL)定义的。它能让你以文本的形式读取Prototype目录的安全信息,以后无须借助Prototype目录即可使用。
  让我们删掉这个测试目录Protected吧,然后在SDDL中保存Prototype目录的安全信息。
PS> Del Protected  
PS> $acl = Get-Acl Prototype
  
PS> $sddl = $acl.Sddl
  
PS> $sddl
  
O:S-1-5-21-2146773085-903363285-719344707-1282827G:DUD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;S-1-5-21-2146773085-903363285-719344707-1282827)(A;OICI;FR;;;S-1-5-21-2575865618-2571387221-2201921913-1000)
  然后把这个SDDL文本保存到第二个脚本中,可将该安全设置赋给任意目录。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  # 创建新目录
  Md Protected
  # 在 SDDL中的是安全描述符 (一行):
  $sddl = "O:S-1-5-21-2146773085-903363285-719344707-1282827G:DUD:PAI(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;S-1-5-21-2146773085-903363285-719344707-1282827)(A;OICI;FR;;;S-1-5-21-2575865618-2571387221-2201921913-1000)"
  # 获取目录的安全描述:
  $acl = Get-Acl Protected
  # 使用SDDL定义替换安全描述 :
  $acl.SetSecurityDescriptorSddlForm($sddl)
  # 保存更新
  Set-Acl Protected $acl
  注意:你的第二个目录是完全独立于Prototype目录的。你所需要做的可能是,借助Prototype目录使用图形用户界面,临时生成一个SDDL安全设置定义。
  然而,SDDL不能很方便的移交给其它机器。如果你仔细看下,每个授权用户不是根据用户名识别,而是根据它们的安全标识符(SID)识别。不同的机器上,即使用户名相同,这个SID也不会相同,因为它们隶属不同的账户。但是在一个域(domain)中,相同名字的账号的SID是相同的,因为域会集中管理。其结果就是SDDL解决方案在基于域环境的公司网络中非常完美。尽管如此,如果你处在一个小型的对等网络中,SDDL也能非常有用。你只需要使用“复制黏贴”去替换SID而已。不过,在对等网络中,cacls 或者 icacls命令可能更简单一点。
手动创建新权限
  权限也可以被手动创建。其优点就是,即使没有集中域,你也可以根据用户名来指定授权用户,这样可以以相同的方式在任意机器上工作。但是注意,它引入了额外的工作,因为你必须完全创建你自己的安全描述符,接下来的例子会展示。但是在实践中发现这个过程非常的耗时。使用cacls和icacls都比它简单一点。现在我们删除掉测试目录Protected,再次创建一个新的目录,让它只有默认的访问权限。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  $acl = Get-Acl Protected
  # 添加第一个规则:
  $person = [System.Security.Principal.NTAccount]"Administrator"
  $access = [System.Security.AccessControl.FileSystemRights]"FullControl"
  $inheritance = [System.Security.AccessControl.InheritanceFlags] "ObjectInherit,ContainerInherit"
  $propagation = [System.Security.AccessControl.PropagationFlags]"None"
  $type = [System.Security.AccessControl.AccessControlType]"Allow"
  $rule = New-Object System.Security.AccessControl.FileSystemAcce***ule( $person,$access,$inheritance,$propagation,$type)
  $acl.AddAcce***ule($rule)
  # 添加第二个规则:
  $person = [System.Security.Principal.NTAccount]"Everyone"
  $access = [System.Security.AccessControl.FileSystemRights]"ReadAndExecute"
  $inheritance = [System.Security.AccessControl.InheritanceFlags] "ObjectInherit,ContainerInherit"
  $propagation = [System.Security.AccessControl.PropagationFlags]"None"
  $type = [System.Security.AccessControl.AccessControlType]"Allow"
  $rule = New-Object System.Security.AccessControl.FileSystemAcce***ule( $person,$access,$inheritance,$propagation,$type)
  $acl.AddAcce***ule($rule)
  # 保存权限更新:
  Set-Acl Protected $acl
  接下来,让我们一起看看每个访问规则是怎么定义的。每一个规则需要5个细节:

  •   Person:这是该规则应当适用的人或者组。
  •   Access:这里选择规则要控制的权限。
  •   Inheritance:这里选择规则要应用的对象。这个规则能够,并且一般是会授予它的子对象,这样它就能自动适用于目录中的文件了。
  •   Propagation:决定权限是否要传递给子对象(比如子目录和文件),通常情况下设置为None,仅仅授予权限。
  •   Type:它能让你设置权限或者限制,如果限制,指定的权限会明确不予批准。
  接下来问题是这些规范允许那些值?这个例子演示通过.NET对象(第六章)显示这些规范。你可以使用下面的机器列出访问权限允许的值:
[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
  如果你想设置权限时,实际上得结合上面列表中列出的相关值,比如:
$access = [System.Security.AccessControl.FileSystemRights]::Read `  
-bor [System.Security.AccessControl.FileSystemRights]::Write
  
$access
  

  
131209
  结果是一个数字,读和写权限的位掩码。在上面的例子中,你可以非常简单第获取相同的结果,因为允许你指定你想要的项目,甚至把它们放在一个逗号分隔项中,紧跟在括号括起来的.NET枚举类型后面。
$access = [System.Security.AccessControl.FileSystemRights]"Read,Write"  
$access
  
   Write, Read
  

  
[int]$access
  
   131209
  因为这里你没有指定二进制计算符-bor,它的结果是可读的文本。而此时需要位掩码来工作,所以把它转换成Integer整形数据类型。你可以像这样随时得出设置的相关值。
[int][System.Security.AccessControl.InheritanceFlags] `  
"ObjectInherit,ContainerInherit"
  
3
  这样做的意义在于你现在可以测试其它.NET枚举类型的值,把它们转换成整数。虽然不能增强你的命令的可读性,但是可以压缩脚本。因为下面的脚本行和前面例子中的脚本行可以做同一件事。
  1
  2
  3
  4
  5
  6
  7
  8
  9
  Del Protected
  Md Protected
  $acl = Get-Acl Protected
  $rule = New-Object System.Security.AccessControl.FileSystemAcce***ule( "Administrator",2032127,3,0,0)
  $acl.AddAcce***ule($rule)
  $rule = New-Object System.Security.AccessControl.FileSystemAcce***ule( "Everyone",131241,3,0,0)
  $acl.AddAcce***ule($rule)
  # 保存更新的权限:
  Set-Acl Protected $acl
  最后,我们看看PowerShell是怎么指定特定用户的权限的。在上面的例子中,你指定了用户或者组的名称,但是权限不能识别用户名,但能识别账号的唯一SID,用户名在内部会被更改成SID,你也可以在脚本中手动更改用户名,看看指定的用户名是否存在。
$Account = [System.Security.Principal.NTAccount]"Administrators"  
$SID = $Account.translate([System.Security.Principal.Securityidentifier])
  
$SID
  
BinaryLengthAccountDomainSidValue
  
---------------------------------
  
16 S-1-5-32-544
  一个NTAccount对象描述了一个权限可以分配的安全主体。在实践中,它是用户和组。NTAccount对象可以使用Translate()来输出它包含的与主体对应的SID。而这只会在指定的账号确实存在的情况下有效。否则,你会得到一个错误。因此你也可以使用Translate()来验证一个账号的存在性。
  通过Translate()获取的SID非常有用。如果你仔细看,你会发现管理员组的SID和你自己当前账号的SID完全不同:
([System.Security.Principal.NTAccount]"$env:userdomain\\$env:username").`  
Translate([System.Security.Principal.Securityidentifier]).Value
  

  
S-1-5-21-2146773085-903363285-719344707-1282827
  

  
([System.Security.Principal.NTAccount]"Administrators").`
  
Translate([System.Security.Principal.Securityidentifier]).Value
  

  
S-1-5-32-544
  管理员组的SID不但很短,而且是唯一的。为了整合这个账号,Windows使用了所谓的众所周知的SID,它在所有的Windows系统中都是相同的。这一点很重要,因为你德文系统中运行上面的脚本会失败。在本地化的德文系统上,因为Administrators组叫做”Administratoren”,”Everyone”组叫做”Jeder”。但是这些账号的SID是相同的。知道了这些组的SID号,你就可以使用它们代替那些本地化的名称了。下面是怎样将SID转换成用户账号的名称:
$sid = [System.Security.Principal.SecurityIdentifier]"S-1-1-0"  
$sid.Translate([System.Security.Principal.NTAccount])
  

  
Value
  
-----
  
Everyone
  这是怎样让你的脚本能够非常完美地在国际本地化机器上运行:
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  Del Protected
  Md Protected
  $acl = Get-Acl Protected
  # 管理员完全控制:
  $sid = [System.Security.Principal.SecurityIdentifier]"S-1-5-32-544"
  $access = [System.Security.AccessControl.FileSystemRights]"FullControl"
  $rule = New-Object System.Security.AccessControl.FileSystemAcce***ule( `
  $sid,$access,3,0,0)
  $acl.AddAcce***ule($rule)
  # 所有用户的只读权限:
  $sid = [System.Security.Principal.SecurityIdentifier]"S-1-1-0"
  $access = [System.Security.AccessControl.FileSystemRights]"ReadAndExecute"
  $rule = New-Object System.Security.AccessControl.FileSystemAcce***ule( `
  $sid,$access,3,0,0)
  $acl.AddAcce***ule($rule)
  # 保存权限更新:
  Set-Acl Protected $acl
  参考:
  http://www.pstips.net/powershell-online-tutorials/



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-561519-1-1.html 上篇帖子: 26. PowerShell -- 文件系统(1) 下篇帖子: 29. PowerShell -- 批量安装msi后缀软件的方法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表