¡ Hola, chicos del scripting! Crear archivos CAB con Windows PowerShell

The Microsoft Scripting Guys

Contenido

Expandir archivos CAB
Mediante la utilidad makecab.exe

uno de mis películas favoritas tecla de (" Juan acceso") fue sobre un chico que era una smuggler datos en el futuro. Podría viaje a distintos países y cumplir con los clientes. Tenía un conector surgically implantado en la base de su encabezado, y los clientes Conecte su dispositivo Zune-buscar en las cantidades masivas conector y la transferencia de datos en su cerebro. Porque este smuggler datos tenía su cerebro con particiones y Evidentemente estaba utilizando permisos de archivos NTFS, no tenga derechos para la parte de smuggling de datos de su cerebro. Los datos, por tanto, eran seguros, incluso desde de la smuggler propio wandering ideas. En una escena, el cliente quería le courier una cantidad masiva de datos, pero evidentemente que nuestro smuggler no tiene una capacidad suficiente para llevar datos tanto a. Por lo que ¿qué? Comprimido su cerebro, me encanta lo!

Compresión de datos no es sólo fodder buena de películas, es igualmente útil para los administradores de red. Todos conocemos y lo más probable es que utilice, varias utilidades de compresión de archivos. Utilizarlas cada semana en el envío por correo electrónico de capítulos de mi libro nuevo en el editor de Microsoft Press. Hacerlo no mucho porque soy ancho de banda restringido, pero como contamos con cuotas de correo electrónico que limitan el tamaño de e-los mensajes que se pueden almacenar, enviar y recibir. También han utiliza utilidades de compresión de archivos cuando se necesitaba archivar archivos desde mi equipo portátil a uno de los discos portátiles varios flotante alrededor de la oficina.

Cuando utiliza para viajes todo el mundo impartir talleres de secuencias de comandos, pedirá de forma rutinaria, "cómo pueden comprimir archivos a través de secuencias de comandos?" Y desea responder, "se necesita comprar una utilidad de terceros que admite modificadores de línea de comandos." Un día, estaba leyendo el registro, y ejecutó a través de un objeto COM (Curiosamente) denominado makecab.makecab. Hmm, ¿qué suponga que ese objeto? Sí, tiene razón. Permite que los archivos de contenedor (.cab), muy comprimido archivos utilizados por diversas aplicaciones para empaquetado y la implementación. Pero no hay nada puede impedir a un administrador de red enterprising o gurú de secuencias de comandos de appropriating estas herramientas por sí mismos. Y eso es sólo lo que haremos todo lo. Comencemos con la secuencia de comandos en la figura 1 .

La figura 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

El lo primero que debemos hacer es crear algunos parámetros de línea de comandos utilizando la instrucción de parámetro. La instrucción de parámetro debe ser la primera línea noncommented en la secuencia de comandos. Cuando la secuencia de comandos se ejecuta desde dentro de la consola de Windows PowerShell desde dentro de un editor de secuencias de comandos, los parámetros de línea de comandos se utilizan o para controlar la forma en que la secuencia de comandos se ejecuta. De este modo, no tendrá que modificar la secuencia de comandos cada vez que desee crear un archivo .CAB desde un directorio diferente. Sólo se necesita proporciona un valor nuevo para el parámetro –filepath, tal como se muestra aquí:

La buena sobre los parámetros de línea de comandos es que utilizan finalización parámetro parcial, lo que significa que es necesario suministrar sólo del parámetro que ser únicos. Por lo tanto, puede utilizar una sintaxis de línea de comandos como ésta:

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

Esta sintaxis se busque el directorio c:\fso1 y obtener todos los archivos. A continuación, podría cree un archivo .CAB denominado bcab.cab en la carpeta fso2 fuera de la unidad c:\. También podría producir información de depuración mientras estaba en ejecución. Tenga en cuenta que el parámetro –debug es un parámetro conmutado, que significa afecta a la secuencia de comandos sólo cuando está presente. Ésta es la sección correspondiente de la secuencia de comandos CreateCab.ps1:

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

Ahora vamos a crear la función nueva de CAB, que acepta dos parámetros de entrada, –path y –files:

Function New-Cab($path,$files)

Puede asignar el IDENTIFICADOR de programa, makecab.makecab a una variable denominada $ makecab, lo que la secuencia de comandos un poco más fácil de leer. Esto es también un buen lugar para colocar la primera instrucción de depuración de escritura:

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

A continuación vamos a crear el objeto COM:

 $cab = New-Object -ComObject $makecab

¿Un bit de comprobación de errores es en orden, que puede realizarse mediante la $? variable automática:

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

Si no hay ningún error ha producido durante el intento de crear el objeto makecab.makecab, puede utilizar el objeto contenido en la variable de cab $ y llamar al método CreateCab:

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

Una vez que se ha creado el archivo .CAB, puede agregar archivos a ella mediante la instrucción ForEach:

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

Cuando haya convertido el nombre de archivo completo en una cadena y quitar la información del directorio mediante el cmdlet de división de ruta de acceso, incluyen otra instrucción de depuración de escritura dejar que el usuario de la secuencia de comandos saber lo que ocurre, esta forma:

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

A continuación agrega el archivo al archivo .CAB:

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

Para cerrar el archivo .CAB, utilice el método CloseCab:

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

Ahora le vamos al punto de entrada de la secuencia de comandos. En primer lugar, compruebe si es que se va a ejecutarse en modo de depuración buscando la variable de depuración $. Si la variable de depuración $ no está presente, no es necesario hacer nada. Si está presente, debe establecer el valor de la variable de DebugPreference $ para continuar, que permite que las instrucciones de escritura de depuración que se imprimirán en la pantalla. De forma predeterminada, se establece la DebugPreference $ en silentlycontinue, lo que significa que vaya más allá de los comandos sin hacer nada. Este es el código:

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

Ahora debe obtener una colección de archivos. Para ello, puede usar el cmdlet Get-ChildItem:

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

A continuación, pasa la colección a la función New-CAB tal como se muestra aquí:

New-Cab -path $path -files $files

Cuando se ejecuta la secuencia de comandos CreateCab.ps1 en modo de depuración, verá salida, como se muestra en La figura 2 .

fig02.gif

La Figura 2 ejecución CreateCab.ps1 en modo de depuración

Expandir archivos CAB

No utilice el objeto makecab.makecab para expandir el archivo .CAB, porque no tiene un método de expansión. Ni se puede utilizar el objeto makecab.expandcab porque no existe. Pero la capacidad para expandir un archivo .CAB es inherente en el shell de Windows, por lo que puede usar el objeto shell. Para obtener acceso al shell, puede utilizar el objeto COM de Shell.Application, tal como se muestra en la secuencia de comandos de ExpandCab.ps1 de la figura 3 .

La figura 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

En primer lugar la secuencia de comandos crea los parámetros de la línea de comandos, tanto como hacía CreateCab.ps1:

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

A continuación crea la ConvertFrom-CAB función, que acepta dos parámetros de línea de comandos, uno que contiene el archivo .CAB y otro que contiene el destino para expandir los archivos:

Function ConvertFrom-Cab($cab,$destination)

Ahora se crea una instancia del objeto Shell.Application, un objeto muy eficaz con un número de métodos útiles. la figura 4 se muestran los miembros del objeto Shell.Application.

Figura 4 miembros del objeto Shell.Application
Nombre MemberType Definición
AddToRecent (Método) Anular AddToRecent (Variant, cadena)
BrowseForFolder (Método) Carpeta BrowseForFolder (int, cadena, int, el valor de tipo Variant)
CanStartStopService (Método) CanStartStopService Variant (string)
CascadeWindows (Método) Anular () CascadeWindows
ControlPanelItem (Método) VOID ControlPanelItem (cadena)
EjectPC (Método) Anular () EjectPC
Explorar (Método) VOID explorar (valor de tipo Variant)
ExplorerPolicy (Método) ExplorerPolicy Variant (string)
FileRun (Método) Anular () FileRun
FindComputer (Método) Anular () FindComputer
FindFiles (Método) Anular () FindFiles
FindPrinter (Método) Anular FindPrinter (cadena, cadena, cadena)
GetSetting (Método) BOOL ObtenerValor (int)
GetSystemInformation (Método) GetSystemInformation Variant (string)
Ayudar a (Método) Anular () de la Ayuda
IsRestricted (Método) int IsRestricted (cadena, cadena)
IsServiceRunning (Método) IsServiceRunning Variant (string)
MinimizeAll (Método) Anular () MinimizeAll
NameSpace (Método) Carpeta NameSpace (valor de tipo Variant)
Abrir (Método) VOID abrir (valor de tipo Variant)
RefreshMenu (Método) Anular () RefreshMenu
ServiceStart (Método) ServiceStart Variant (string, valor de tipo Variant)
ServiceStop (Método) ServiceStop Variant (string, valor de tipo Variant)
SetTime (Método) Anular () SetTime
ShellExecute (Método) Anular ShellExecute (cadena, valor de tipo Variant, Variant, valor de tipo Variant, valor de tipo Variant)
ShowBrowserBar (Método) ShowBrowserBar Variant (string, valor de tipo Variant)
ShutdownWindows (Método) Anular () ShutdownWindows
Suspender (Método) Anular () Suspend
TileHorizontally (Método) Anular () TileHorizontally
TileVertically (Método) Anular () TileVertically
ToggleDesktop (Método) Anular () ToggleDesktop
TrayProperties (Método) Anular () TrayProperties
UndoMinimizeALL (Método) Anular () UndoMinimizeALL
Windows (Método) Windows IDispatch)
WindowsSecurity (Método) Anular () WindowsSecurity
Aplicación Propiedad Aplicación de IDispatch () {get}
Principal Propiedad Matriz de IDispatch () {get}

Puesto que desea utilizar más de una vez el nombre del objeto COM, es conveniente asignar el IDENTIFICADOR de programa del objeto COM a una variable. Podrá utilizar la cadena con el cmdlet New-Object, así como al proporcionar comentarios para el usuario. Esta es la línea de código que asigna el IDENTIFICADOR de programa Shell.Application a una cadena.

{
 $comObject = "Shell.Application"

Para proporcionar comentarios, puede usar el cmdlet Write-depuración con un mensaje que se está intentando crear el objeto Shell.Application:

 Write-Debug "Creating $comObject"

A continuación realmente crearemos el objeto:

 $shell = New-Object -Comobject $comObject

A continuación, nos probar los errores. Para ello, se puede utilizar la variable $ automática?, que indica si el comando último se ha completado correctamente. Es un booleano verdadero/falso. Puede utilizar este hecho para simplificar el código. Utiliza el no operador,!, junto con un si instrucción. Si la variable no es true, utilizar la instrucción throw para provocar un error y detiene la ejecución de la secuencia de comandos, de este modo:

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

Si la secuencia de comandos correctamente crea el objeto Shell.Application, se proporcionan algunos comentarios:

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

El paso siguiente en la operación consiste en conectar con el archivo .CAB. Para ello, puede utilizar el método de espacio de nombres del objeto Shell.Application. Se trata de otro paso importante, por lo que tiene sentido usar otra instrucción de depuración de escritura como un indicador de progreso para el usuario:

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

Ahora nos conectamos a la carpeta de destino. Para ello, se utilice el método espacio de nombres, a continuación, otra instrucción de depuración de escritura para hacer el saber qué carpeta de usuario realmente estaba conectado a:

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

Con todos los que preparación fuera de la vista, la capacidad de respuesta del comando real para expandir el archivo .CAB es algo anticlimactic. Utiliza el método de CopyHere desde el objeto de carpeta que se almacena en la variable de CarpetaDeDestino $. Da la referencia al archivo .cab que se almacena en la variable de sourceCab $ como el parámetro de entrada de este modo:

 $DestinationFolder.CopyHere($sourceCab)
}

El punto de partida para la secuencia de comandos hace dos cosas. En primer lugar, comprueba la presencia de la variable de depuración $. Si $ depuración está presente, establece la debugPreference $ para continuar obligar el cmdlet Write-Debug para imprimir los mensajes en la ventana de la consola. En segundo lugar, llama a la función ConvertFrom-CAB y pasa la ruta de acceso al archivo .cab desde el parámetro de la línea de comandos –cab y el destino de los archivos expandidos desde el parámetro –destination:

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

Cuando ejecuta la secuencia de comandos ExpandCab.ps1 en modo de depuración, verá resultado similar a la de La figura 5 .

fig05.gif

La figura 5 ExpandCab.ps1 en ejecución en modo de depuración

Mediante la utilidad makecab.exe

Si ejecuta estas dos secuencias de comandos en Windows Server 2003 o Windows XP, no tendrá ningún problema, pero no existe el objeto COM de makecab.makecab en Windows Vista o posterior. Este misfortune no detener los scripter determinado, sin embargo, porque siempre se puede usar la utilidad makecab.exe desde la línea de comandos. Para ello, puede utilizar la secuencia de comandos CreateCab2.ps1, mostrada en la figura 6 .

Figura 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

Igual que con las demás secuencias de comandos, CreateCab2.ps1 primero crea un par de parámetros de la línea de comandos:

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

Cuando se ejecuta la secuencia de comandos con el modificador –debug, el resultado será similar a lo que se muestra en La figura 7 .

fig07.gif

La figura 7 CreateCab2.ps1 en ejecución en modo de depuración

A continuación la secuencia de comandos crea la función DDF de nuevo, que crea un archivo .ddf básica que se utiliza en el programa MakeCab.exe para crear el archivo .CAB. La sintaxis de estos tipos de archivos está documentada en el Contenedor de Microsoft Software Development Kit. Después de utilizar la palabra clave de función para crear la función DDF de nuevo, utilizar el cmdlet de combinación de ruta de acceso para crear la ruta de archivo en el archivo .ddf temporal. Puede concatenar la unidad, la carpeta y el nombre de archivo junto, pero esto puede ser una operación engorroso y propensa. Como práctica recomendada, siempre se debe usar el cmdlet de combinación de ruta de acceso para crear sus rutas, de este modo:

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

Para proporcionar un poco de información al usuario si la secuencia de comandos se ejecuta con el, conmutador de depuración, utilice el cmdlet Write-Debug tal como se muestra aquí:

 Write-Debug "DDF file path is $ddfFile"

Ahora debe crear la primera parte del archivo .ddf. Para ello, puede utilizar una expansión aquí de cadena, lo que significa que no tendrá que relacionados con usted mismo con secuencias de escape de caracteres especiales. Como ejemplo, comentarios en un archivo .ddf están precedidos por un punto y coma, que es un carácter reservado en Windows PowerShell. Si se intenta crear este texto sin una cadena de aquí, deberá escape cada uno de los puntos y comas para evitar errores de tiempo de compilación. Mediante una expansión aquí de cadena, puede sacar partido de la expansión de variables. Un aquí-cadena comienza con la y comercial y unas comillas y termina con unas comillas y una " y " comercial:

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

A continuación puede que desee agregar comentarios mediante el cmdlet Write-depuración:

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

Ahora hemos llegado a la parte que podría causar problemas. El archivo .ddf debe ser ASCII puro. De forma predeterminada, Windows PowerShell usa Unicode. Para asegurarse de que tiene un archivo ASCII, debe utilizar el Out-File cmdlet. La mayor parte del tiempo, puede evitar utilizando Out-File empleando las flechas de redirección archivo, pero esto no es una de esas ocasiones. Ésta es la sintaxis:

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

Probablemente desea proporcionar alguna información de depuración más con escritura de depuración antes de recopilar la colección de archivos mediante el cmdlet Get-ChildItem:

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

Es importante filtrar carpetas de la colección debe MakeCab.exe no es capaz de comprimir las carpetas. Para ello, utilice la WHERE-Object con un no operador que indica el objeto no es un contenedor:

 Where-Object { !$_.psiscontainer } |

A continuación, deberá trabajar con cada archivo como lo que respecta a través de la canalización. Para ello, utilice el cmdlet ForEach-Object. Porque ForEach-Object es un cmdlet, en contraposición a una instrucción del lenguaje, las llaves deben estar en la misma línea que el nombre de cmdlet ForEach-Object. El problema con esto es que tiene una tendencia a enterrar las llaves en el código. Como práctica recomendada, desea alinear las llaves a menos que el comando sea muy corto como el comando WHERE-Object anterior. Para ello, sin embargo, requiere el uso del carácter de aviso de continuación de línea (backtick). SE sabe que algunos desarrolladores que evitar la continuación de línea como el plague, pero creo lining hasta entre llaves es más importante ya que permite el código sea más fácil de leer. Aquí es el principio del cmdlet ForEach-Object:

 ForEach-Object `

Porque el archivo de .ddf utilizado por MakeCab.exe es texto ASCII, deberá convertir la propiedad fullname del objeto System.IO.fileinfo devuelto por el cmdlet Get-ChildItem a una cadena. Además, dado que es posible que tienen archivos con espacios en sus nombres, que tiene sentido encierra el valor de fullname de archivo en un conjunto de comillas:

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

, A continuación, canalizar los nombres de archivo para el Out-File cmdlet, asegurándose de que para especificar la codificación de ASCII y utilizar el modificador –append para evitar sobrescribir todo lo demás en el archivo de texto:

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

Ahora puede proporcionar otra actualización a los usuarios de depuración y, a continuación, invocar la función CAB de nuevo:

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

Al especificar la función CAB de nuevo, también puede proporcionar esa información al usuario:

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

A continuación, si la secuencia de comandos se ejecuta con el modificador –debug, puede utilizar parámetro /V del MakeCab.exe para proporcionar información de depuración detallada (3 es detalle completa; 0 es none). Si no se ejecuta la secuencia de comandos con el modificador –debug, no desea atestar la pantalla con demasiada información para que debe cumplir con predeterminado la utilidad:

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

El punto de entrada a la secuencia de comandos comprueba ver si la $ depuración variable está presente. Si es así, la variable automático de debugPreference $ está establecida para continuar y, se mostrará información de depuración mediante el cmdlet Write-depuración. Después de haber realizado que verificación, se llama al cmdlet New-DDF con los dos valores proporcionados en la línea de comandos: la ruta de acceso y el rutadearchivo:

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

Bien, que es sobre ella de artículo de este mes en comprimir y descomprimir archivos. Si piensa como el encabezado se va a expandir, puede intentar conecte su Zune a su ear y vea si puede ejecutar una de estas secuencias de comandos para comprimir el cerebro de parte. Que but…I me indica now…it derecho sólo funciona en las películas. Lo sentimos. Si no se bloquea hasta, debe detener mediante el Centro de secuencias de comandos de TechNet y desproteger ¡ nuestra diaria "Hola, chicos secuencias de comandos del!" artículo. Ver no existe.

Ed Wilson , un experto de secuencias de comandos conocido, es el autor de ocho libros, entre ellos Windows PowerShell Scripting Guide (2008) y Microsoft Windows PowerShell Step by Step (2007). Ed contiene más de 20 certificaciones del sector, incluidos Microsoft Certified Systems Engineer (MCSE) y Certified Information Systems Security Professional (CISSP). En su tiempo libre, disfruta woodworking, fotografía underwater y scuba diving. Y té.

Craig Liebendorfer es wordsmith y longtime editor Web de Microsoft. Craig aún no se puede creer hay un trabajo que se paga a trabajar con las palabras cada día. Uno de sus cosas favoritas es irreverent humor, para que debe ajustarse derecha en aquí. Considera que su accomplishment mayor en la vida que su hija magnificent.