嗨,脚本专家 ! 使用 Windows PowerShell 创建 CAB 文件

The Microsoft Scripting Guys

内容

展开 Cab 文件
使用 makecab.exe 实用程序

之一我 喜欢电影"Johnny 助记符") 是关于在将来被数据 smuggler 的一个人。 他将移动到不同的国家 / 地区并满足与客户端。 他 surgically implanted 到其头的一个 Jack,客户将插入该插孔和传输大量数据到他的大脑 Zune 查找设备。 因为此数据 smuggler 他的大脑分区 evidently 使用 NTFS 文件权限,他没有到他的大脑数据 smuggling 部分的权利。 数据已因此安全甚至是来自该 smuggler 的自己 wandering 想法。 在一个场景中, 客户端希望他 Courier 大容量长的一数据,但 evidently 我们 smuggler 没有足够大容量来执行如此多的数据。 那么做他怎么呢? 他压缩他的大脑,我喜欢它!

数据压缩不只好 fodder 电影,同样有用网络管理员。 我们所有知道,最有可能使用,各种文件压缩实用程序。 我使用它们在通过电子邮件发送到我编辑器在 Microsoft Press 的章节我新工作簿的每周。 我这样做不,许多,因为我带宽限制,但因为我们有限制的电子邮件我们可以存储大小的电子邮件配额,收发。 我需要存档从我的便携式计算机的多个浮动解决我的 Office 的可移植磁盘的文件时,我还必须使用文件压缩实用工具。

我使用讲授脚本中心的研讨会世界各地旅行,我会要求定期,"如何可以我压缩的文件通过脚本吗?" 并且我想回答,"则需要购买一个支持命令行开关的第三方实用程序"。 某的一天我已通过在的注册表读我运行在名为 (有趣) 的 COM 对象 Makecab.Makecab。 嗯,内容您假设该对象会? yep,您说对了。 它可以使压缩 (.cab) 文件高压缩各种应用程序用于打包和部署文件。 但没有任何操作,停止 enterprising 的网络管理员或从这些工具 appropriating 自己的脚本专家。 而这只是我们要做。 让我们从 图 1 中的脚本开始。

图 1 CreateCab.ps1

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso\aCab.cab",
  [switch]$debug
  )
Function New-Cab($path,$files)
{
 $makecab = "makecab.makecab"
 Write-Debug "Creating Cab path is: $path"
 $cab = New-Object -ComObject $makecab
 if(!$?) { $(Throw "unable to create $makecab object")}
 $cab.CreateCab($path,$false,$false,$false)
 ForEach ($file in $files)
 {
 $file = $file.fullname.tostring()
 $fileName = Split-Path -path $file -leaf
 Write-Debug "Adding from $file"
 Write-Debug "File name is $fileName"
 $cab.AddFile($file,$filename)
 }
 Write-Debug "Closing cab $path"
 $cab.CloseCab()
} #end New-Cab

# *** entry point to script ***
if($debug) {$DebugPreference = "continue"}
$files = Get-ChildItem -path $filePath | Where-Object { !$_.psiscontainer }
New-Cab -path $path -files $files

CreateCab.ps1 –filepath C:\fso1

首先我们要做是创建使用参数语句一些命令行参数。 在参数语句必须在脚本中 noncommented 的首行。 时从 Windows PowerShell 控制台中运行脚本或从中在脚本编辑器中,命令行参数使用来控制脚本执行的方式。 以这种方式,您没有编辑您要从不同的目录中创建一个.cab 文件的每个时间的脚本。 您需要仅为提供一个新值 / –filepath 参数如下所示:

命令行参数,好就是使用这意味着您需要提供的部分参数完成唯一参数,它是唯一。 因此,您可以使用如下命令行语法:

CreateCab.ps1 –f c:\fso1 –p c:\fso2\bcab.cab –d

此语法将搜索 c:\fso1 目录,并获取所有文件。 然后,它将创建名为 bcab.cab 关闭 c:\ 驱动器 fso2 文件夹中的一个 CAB 文件。 运行时,它还将生成调试信息。 请注意 –debug 参数是意味着它影响该脚本,而存在时,仅一个交换的参数。 下面是 CreateCab.ps1 脚本的相关节:

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso\aCab.cab",
  [switch]$debug
  )

现在我们将创建新 cab 函数,接受两个输入的参数、 –path 和 –files 的:

Function New-Cab($path,$files)

您可以分配程序 ID,Makecab.Makecab 为一个变量名为 $ Makecab,这将使脚本有点易于阅读。 这也是最好将第一个写调试语句:

{
 $makecab = "makecab.makecab"
 Write-Debug "Creating Cab path is: $path"

接下来将创建 COM 对象:

 $cab = New-Object -ComObject $makecab

一些错误检查是为了可以通过使用 $ 的? 自动变量:

 if(!$?) { $(Throw "unable to create $makecab object")}

如果没有错误发生在尝试创建 Makecab.Makecab 对象,可以使用 $ cab 变量中包含的对象,并调用该 CreateCab 方法:

 $cab.CreateCab($path,$false,$false,$false)

在创建.cab 文件后您可以通过使用 ForEach 语句向其中添加文件:

 ForEach ($file in $files)
 {
 $file = $file.fullname.tostring()
 $fileName = Split-Path -path $file -leaf

在转换字符串的完整文件名并删除目录信息使用拆分路径 cmdlet 之后,则包含另一个写入调试语句,以便脚本的用户知道正在运行的内容,这种方式:

 Write-Debug "Adding from $file"
 Write-Debug "File name is $fileName"

接下来将文件添加到 CAB 文件中:

 $cab.AddFile($file,$filename)
 }
 Write-Debug "Closing cab $path"

若要关闭该 Cabinet 文件,您可以使用 CloseCab 方法:

 $cab.CloseCab()
} #end New-Cab

现在我们将请转到脚本的入口点。 首先我们检查以查看是否脚本在运行在调试模式下通过查找 $ 调试变量。 如果 $ 调试变量不存在,您不必执行任何操作。 如果有存在需要设置 $ DebugPreference 变量来继续,这样写调试语句,要在屏幕上打印的值。 默认情况下, $ DebugPreference 是设置为该方法在不执行任何操作跳过该命令的 silentlycontinue。 下面的代码:

if($debug) {$DebugPreference = "continue"}

现在,您需要获取文件的集合。 为此,您可以使用 Get-ChildItem cmdlet:

$files = Get-ChildItem -path $filePath | 
 Where-Object { !$_.psiscontainer }

然后您传递集合给新 cab 函数如下所示:

New-Cab -path $path -files $files

您在调试模式下运行 CreateCab.ps1 脚本时, 您会看到输出所示 图 2 .

fig02.gif

图 2 运行 CreateCab.ps1 在调试模式下

展开 Cab 文件

您不能使用 Makecab.Makecab 对象以展开该 Cabinet 文件,因为它不会有一个方法。 也可以使用 Makecab.expandcab 对象因为它不存在。 但可以展开压缩的文件是 Windows 外壳程序中固有,因此您可以使用 Shell 对象。 若要访问在外壳程序,可以使用 Shell.application COM 对象,ExpandCab.ps1 脚本在 图 3 所示。

图 3 ExpandCab.ps1

Param(
  $cab = "C:\fso\acab.cab",
  $destination = "C:\fso1",
  [switch]$debug
  )
Function ConvertFrom-Cab($cab,$destination)
{
 $comObject = "Shell.Application"
 Write-Debug "Creating $comObject"
 $shell = New-Object -Comobject $comObject
 if(!$?) { $(Throw "unable to create $comObject object")}
 Write-Debug "Creating source cab object for $cab"
 $sourceCab = $shell.Namespace($cab).items()
 Write-Debug "Creating destination folder object for $destination"
 $DestinationFolder = $shell.Namespace($destination)
 Write-Debug "Expanding $cab to $destination"
 $DestinationFolder.CopyHere($sourceCab)
}

# *** entry point ***
if($debug) { $debugPreference = "continue" }
ConvertFrom-Cab -cab $cab -destination $destination

第一次该脚本创建在命令行的参数与 CreateCab.ps1 做:

Param(
  $cab = "C:\fso\acab.cab",
  $destination = "C:\fso1",
  [switch]$debug
  )

接下来它创建在 ConvertFrom cab 函数,其接受两个命令行参数、 一个包含该.cab 文件和一个包含目标展开这些文件:

Function ConvertFrom-Cab($cab,$destination)

现在,您创建 Shell.Application 对象一个具有许多有用的方法的非常强大对象的实例。 图 4 显示了 Shell.Application 对象的成员。

图 Shell.Application 对象的 4 个成员
名称 MemberType 定义
AddToRecent 方法 void AddToRecent (变量,字符串)
BrowseForFolder 方法 文件夹 BrowseForFolder int、 字符串 int,Variant 类型)
CanStartStopService 方法 变量 CanStartStopService (字符串)
CascadeWindows 方法 void CascadeWindows ()
ControlPanelItem 方法 void ControlPanelItem (字符串)
EjectPC 方法 void EjectPC ()
浏览 方法 void 浏览 (变量)
ExplorerPolicy 方法 变量 ExplorerPolicy (字符串)
FileRun 方法 void FileRun ()
FindComputer 方法 void FindComputer ()
FindFiles 方法 void FindFiles ()
FindPrinter 方法 void FindPrinter (字符串、 字符串、 字串)
GetSetting 方法 bool GetSetting (int)
GetSystemInformation 方法 变量 GetSystemInformation (字符串)
帮助 方法 void 帮助 ()
IsRestricted 方法 int IsRestricted (字符串、 字串)
IsServiceRunning 方法 变量 IsServiceRunning (字符串)
MinimizeAll 方法 void MinimizeAll ()
NameSpace 方法 文件夹 NameSpace (变量)
打开 方法 void 打开 (变量)
RefreshMenu 方法 void RefreshMenu ()
ServiceStart 方法 变量 ServiceStart (string,Variant 类型)
ServiceStop 方法 变量 ServiceStop (string,Variant 类型)
SetTime 方法 void SetTime ()
ShellExecute 方法 void ShellExecute (string,Variant 类型、 Variant 类型、 Variant 类型、 Variant 类型)
ShowBrowserBar 方法 变量 ShowBrowserBar (string,Variant 类型)
ShutdownWindows 方法 void ShutdownWindows ()
挂起 方法 void 挂起 ()
TileHorizontally 方法 void TileHorizontally ()
TileVertically 方法 void TileVertically ()
ToggleDesktop 方法 void ToggleDesktop ()
TrayProperties 方法 void TrayProperties ()
UndoMinimizeALL 方法 void UndoMinimizeALL ()
Windows 方法 IDispatch Windows ()
WindowsSecurity 方法 void WindowsSecurity ()
应用程序 属性 IDispatch 应用程序 () {获取}
属性 IDispatch Parent) {获取}

因为您将要多次使用 COM 对象的名称是要给一个变量的 COM 对象的程序 ID 的好做法。 您将能够使用该字符串与该新对象的 Cmdlet 以及向用户提供反馈时。 下面是代码的 shell.application 程序 ID 为字符串行。

{
 $comObject = "Shell.Application"

若要提供反馈,可以使用写调试 cmdlet 要创建 shell.Application 对象的消息:

 Write-Debug "Creating $comObject"

接下来我们将实际创建对象:

 $shell = New-Object -Comobject $comObject

然后我们测试的错误。 若要这样做,可以使用自动变量 $?,它将告诉您是否成功完成最后一个命令。 Boolean 类型的值的真 / 假。 可以使用这一事实来简化编码。 使用 Not 运算符,!,结合 if 语句。 如果该变量不满足您将使用引发错误并中止这样的该的脚本的执行的 throw 语句:

 if(!$?) { $(Throw "unable to create $comObject object")}

如果该脚本成功地创建 shell.Application 对象,我们提供一些反馈:

 Write-Debug "Creating source cab object for $cab"

在操作中的下一步是连接到该.cab 文件。 为此,可以使用命名空间方法从 shell.Application 对象。 这是另一个重要的步骤,因此很有意义用作另一个写调试语句进度指示器用户:

 $sourceCab = $shell.Namespace($cab).items()
 Write-Debug "Creating destination folder object for $destination"

现在,我们连接到目标文件夹。 为此,您使用该命名空间方法,然后让用户知道哪个文件夹的另一个写调试语句实际上连接到:

 $DestinationFolder = $shell.Namespace($destination)
 Write-Debug "Expanding $cab to $destination"

所有的准备开,扩展 CAB 文件的实际的命令是有些 anticlimactic。 使用 CopyHere 方法从 $ DestinationFolder 变量中存储的文件夹对象。 授予对.cab 文件中存储的 $ sourceCab 变量作为输入参数这种方式在引用:

 $DestinationFolder.CopyHere($sourceCab)
}

起始点到脚本执行两种操作。 首先,它检查 $ 调试变量存在。 如果存在 $ 调试,它设置继续强制写入调试 cmdlet 输出消息控制台窗口 $ debugPreference。 第二个,它调用 ConvertFrom cab 函数,并将路径传递给 cab 文件中,从 –cab 命令行参数和目标为扩展文件从 –destination 参数:

if($debug) { $debugPreference = "continue" }
ConvertFrom-Cab -cab $cab -destination $destination

您在调试模式下运行 ExpandCab.ps1 脚本时, 将看到类似于中的输出 图 5 .

fig05.gif

图 5 在调试模式下的运行 ExpandCab.ps1

使用 makecab.exe 实用程序

如果您在 Windows Server 2003 或 Windows XP 上运行这些两个脚本,请您不会有任何问题中,但在 Windows Vista 或更高版本,不存在 Makecab.Makecab COM 对象。 此 misfortune 不会但是,停止确定为脚本程序,因为可以始终使用 makecab.exe 实用工具从命令行。 这样做可以使用 图 6 中显示该 CreateCab2.ps1 脚本。

图 6 CreateCab2.ps1

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso1\cabfiles",
  [switch]$debug
  )
Function New-DDF($path,$filePath)
{
 $ddfFile = Join-Path -path $filePath -childpath temp.ddf
 Write-Debug "DDF file path is $ddfFile"
 $ddfHeader =@"
;*** MakeCAB Directive file
;
.OPTION EXPLICIT      
.Set CabinetNameTemplate=Cab.*.cab
.set DiskDirectory1=C:\fso1\Cabfiles
.Set MaxDiskSize=CDROM
.Set Cabinet=on
.Set Compress=on
"@
 Write-Debug "Writing ddf file header to $ddfFile" 
 $ddfHeader | Out-File -filepath $ddfFile -force -encoding ASCII
 Write-Debug "Generating collection of files from $filePath"
 Get-ChildItem -path $filePath | 
 Where-Object { !$_.psiscontainer } |
 ForEach-Object `
 { 
 '"' + $_.fullname.tostring() + '"' | 
 Out-File -filepath $ddfFile -encoding ASCII -append
 }
 Write-Debug "ddf file is created. Calling New-Cab function"
 New-Cab($ddfFile)
} #end New-DDF

Function New-Cab($ddfFile)
{
 Write-Debug "Entering the New-Cab function. The DDF File is $ddfFile"
 if($debug)
 { makecab /f $ddfFile /V3 }
 Else
 { makecab /f $ddfFile }
} #end New-Cab

# *** entry point to script ***
if($debug) {$DebugPreference = "continue"}
New-DDF -path $path -filepath $filepath

与其他脚本 CreateCab2.ps1 首次创建几个命令行参数:

Param(
  $filepath = "C:\fso", 
  $path = "C:\fso1\cabfiles",
  [switch]$debug
  )

使用 –debug 开关运行该脚本时,输出将类似于所示 图 7 .

fig07.gif

图 7 运行 CreateCab2.ps1 在调试模式下

接下来该脚本将创建该新 DDF 的函数,创建 MakeCab.exe 程序创建.cab 文件使用一个基本的.ddf 文件。 中介绍了这些类型文件的语法, Microsoft Cabinet 软件开发工具包. 使用函数关键字创建新 DDF 函数后,您可以使用该连接路径 cmdlet 创建临时的.ddf 文件的文件路径。 驱动器、 文件夹和一起,文件名可以连接,但这可能是一个麻烦和容易出错的操作。 最佳做法是您始终应使用联接路径 cmdlet 建立这种方式将文件路径:

Function New-DDF($path,$filePath)
{
 $ddfFile = Join-Path -path $filePath -childpath temp.ddf

为用户提供的反馈如果脚本运行,调试开关使用写 Debug cmdlet,如下所示:

 Write-Debug "DDF file path is $ddfFile"

现在,您需要创建.ddf 文件的第一部分。 为此,您可以使用的展开下面的字符串,这意味着您不必考虑自己使用转义特殊字符。 例如,.ddf 文件中的注释一个以分号是 Windows PowerShell 中的保留的字符开头。 如果您尝试创建此文本不在此处的字符串,需要转义每个以避免出现编译时错误分号。 通过展开下面的字符串您可以利用变量的扩展。 下面的字符串将开始使用与符号和一个引号,并结束一个引号和一个 and 符:

 $ddfHeader =@"
;*** MakeCAB Directive file
;
.OPTION EXPLICIT      
.Set CabinetNameTemplate=Cab.*.cab
.set DiskDirectory1=C:\fso1\Cabfiles
.Set MaxDiskSize=CDROM
.Set Cabinet=on
.Set Compress=on
"@

接下来可能要添加反馈通过写调试 cmdlet:

 Write-Debug "Writing ddf file header to $ddfFile" 

现在,我们都可能会导致一些问题的部分。 将.ddf 文件必须是纯的 ASCII 文件。 默认,Windows PowerShell 使用 Unicode。 要确保您有一个 ASCII 文件,则必须使用在 out-file cmdlet。 大多数情况下,可以避免使用 out-file 通过使用文件重定向箭头但这不是这些情况之一。 下面是语法:

 $ddfHeader | Out-File -filepath $ddfFile -force 
-encoding ASCII

您可能希望提供一些更多的调试信息写入调试之前通过 Get-ChildItem cmdlet 收集您的文件集合:

 Write-Debug "Generating collection of files from $filePath"
 Get-ChildItem -path $filePath | 

值得筛选出集合中的文件夹,因为 MakeCab.exe 不能压缩文件夹。 为此,请使用位置的对象具有一个不显示该对象的运算符不是容器:

 Where-Object { !$_.psiscontainer } |

接下来,您需要使用每个文件,因为它遇到管道。 为此,使用 ForEach-Object cmdlet。 ForEach-Object 是 Cmdlet 相对于一个语言语句大括号都必须 ForEach-Object cmdlet 名称所在的行上。 此该问题是它倾向于在代码中欺骗大括号。 最佳做法是,我喜欢对齐大括号,除非该命令是非常短,如前面的位置对象命令。 为此,但是,要求使用行延续字符 (backtick)。 我知道避免行继续像在的 plague 一些人员,但我认为最大括号组织是更重要,因为它使代码更容易阅读。 下面是 ForEach-Object cmdlet 的开头:

 ForEach-Object `

由于使用 MakeCab.exe.ddf 文件是 ASCII 文本,需要将 System.IO.Fileinfo 对象返回字符串的 Get-ChildItem cmdlet,fullname 属性。 此外,因为您可能具有的名称中的空格的文件,有意义文件 fullname 值括在引号内一组:

 { 
 '"' + $_.fullname.tostring() + '"' | 

然后对其管道到文件名在 out-file cmdlet,确保指定 ASCII 编码,并以避免覆盖所有其他文本文件中使用 –append 开关:

 Out-File -filepath $ddfFile -encoding ASCII -append
 }

现在,可以向调试用户的另一个更新,然后调用新 cab 函数:

 Write-Debug "ddf file is created. Calling New-Cab function"
 New-Cab($ddfFile)
} #end New-DDF

输入新 cab 函数时, 您还可以提供用户信息:

Function New-Cab($ddfFile)
{
 Write-Debug "Entering the New-Cab function. The DDF File is $ddfFile"

下一步中,如果使用 –debug 开关运行该脚本,可以使用 MakeCab.exe 的 / V 参数提供详细的调试信息 (3 是完整的详细信息,0 表示无)。 如果该脚本不运行使用 –debug 开关,不想要 clutter 太多信息屏幕,因此应继续使用实用工具的默认:

 if($debug)
 { makecab /f $ddfFile /V3 }
 Else
 { makecab /f $ddfFile }
} #end New-Cab

入口点,脚本检查以查看是否 $ 调试变量存在。 如果是,$ debugPreference 自动变量设置为继续,并调试信息将显示通过写调试 cmdlet。 已执行检查之后,使用两个值提供给命令行调用 New DDF cmdlet: 路径和在 filepath:

if($debug) {$DebugPreference = "continue"}
New-DDF -path $path -filepath $filepath

和,这是关于该压缩和解压缩文件的这个月的文章。 如果您认为像头是分解,可以尝试在 Zune 插入您的耳并查看如果可以运行这些脚本压缩属于您的大脑之一。 But…I 告诉右 now…it 只工作影片。 很抱歉。 如果未设置锁定,应通过 TechNet脚本中心停止并查看我们的日常您"好,脚本专家 !"文章。 没有看到您。

Ed Wilson ,一个已知的脚本专家是八个书籍,包括 Windows PowerShell Scripting Guide (2008) 和 Microsoft Windows PowerShell Step by Step 的作者 (2007)。 Ed 包含超过 20 个包括 Microsoft 认证系统工程 (MCSE) 和认证信息系统安全专业人员 (CISSP) 行业认证。 在他空闲的时间他受 woodworking、 underwater 摄影和 scuba 所在。 和工作。

Craig Liebendorfer 是 wordsmith 和 longtime 的 Microsoft Web 编辑器。 Craig 仍不能相信还有支付他使用单词每天的作业。 他最喜欢的事情之一是 irreverent 幽默,因此他应适合在此处的右侧。 他认为他的最大 accomplishment 为他你的女儿生命周期中。