2014-05-31

Bulk Extension Renamer - bash

As a quick counter-point to my earlier PowerShell script, here's my equivalent in bash:


1:  #!/bin/bash  
2:    
3:  if [ $# -lt 2 ]; then  
4:      echo -e "\e[33mUsage: $0 <current extension> <new extension>\e[0m"  
5:      exit 1  
6:  fi  
7:    
8:  for fileName in *.$1; do  
9:      newFileName=${fileName/.$1/.$2}  
10:      if [ -f "$newFileName" ]; then  
11:          echo -e "\e[31mNot renaming \e[1m$fileName\e[22m to \e[1m$newFileName\e[22m -- File exists."  
12:      elif [ -d "$newFileName" ]; then  
13:          echo -e "\e[31mNot renaming \e[1m$fileName\e[22m to \e[1m$newFileName\e[22m -- Is a directory."  
14:      else  
15:                 if [ -w "$fileName" ]; then  
16:                      mv "$fileName" "$newFileName"  
17:                       echo -e "\e[1;32m$fileName\e[22m renamed to \e[1m$newFileName\e[0m"  
18:                 else  
19:                      echo -e "\e[31mUnable to rename \e[1m$fileName\e[22m - file is not writable\e[0m"  
20:                 fi  
21:      fi  
22:  done  

Short, neat and to the point. The only slightly unwieldy part is the rather ugly ANSI color codes.

As for functionality, it checks if the file already exists (or a directory with the same name exists) and throws an error if so. It also tests if the original file is writable before attempting to move it and errors out if it isn't.

Here it is running:
 

Change the ComputerName value in Unattend.xml using PowerShell

A quick and dirty PowerShell script to update the value of ComputerName in Unattend.xml before imaging.

This particular value is located at:

<unattend>
 <settings pass="generalize">
  <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <computername>

Which is a bit of a mouthful, but we can be lazy and just pull the value from the nth component node (in my case it's 3, or the 4th component node). PowerShell uses dots to describe the hierarchical path, which looks a lot neater than the above:

$xml.unattend.settings.component[3].computername


To repeat, the value of .component[n] will change depending on the structure of the file.

The value of the computername node can be changed by just providing the new value:

$xml.unattend.settings.component[3].computername = "$computerName"


The imported XML then needs to be saved back to the file on disk:

$xml.save("$unattendFile")


1:  param(  
2:       [parameter(Mandatory = $true)]  
3:       [ValidateNotNullOrEmpty()]  
4:       [string]$computerName  
5:  )  
6:    
7:  Set-ExecutionPolicy Unrestricted  
8:    
9:  $systemDrive = $env:systemdrive  
10:  $unattendFile = "$systemDrive\Unattend.xml"  
11:    
12:  [xml]$xml = get-content $unattendFile  
13:    
14:  $currentName = $xml.unattend.settings.component[3].computername  
15:  write-host "Current Name: "$currentName"`n"  
16:    
17:  $xml.unattend.settings.component[3].computername = "$computerName"  
18:    
19:  $newName = $xml.unattend.settings.component[3].computername  
20:  write-host "New Name: "$newName"`n"  
21:    
22:  $xml.save("$unattendFile")  

Customized Windows PE 5.0

A quick run-through covering creating a customized Windows PE bootable drive, mainly so I don't space out and forget any of the steps (*ahem* again).

The below is based on the Deployment and Imaging tools available by installing Windows ADK 8.1


Create Windows PE files

Run Deployment and Imaging Tools Environment as Administrator

Create the base structure with copype command:

copype <architecture> <directory>

Example for 64 bit:
copype amd64 c:\winpe64

Example for 32 bit:
copype x86 c:\winpe32


Paths to packages

amd64:
\Program Files (x86)\Windows Kits\8.1\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs


x86:
\Program Files (x86)\Windows Kits\8.1\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\WinPE_OCs



Mount boot.wim

(assuming current path is the root of WinPE, e.g., c:\winpe)

dism /mount-wim /wimfile:media\sources\boot.wim /index:1 /mountdir:mount




Adding Key Packages

(assuming current path is \Program Files (x86)\Windows Kits\8.1\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs)


1:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-Scripting.cab  
2:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-Scripting_en-us.cab  
3:    
4:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-WMI.cab  
5:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-WMI_en-us.cab  
6:    
7:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-MDAC.cab  
8:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-MDAC_en-us.cab  
9:    
10:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-HTA.cab  
11:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-HTA_en-us.cab  
12:    
13:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-NetFx.cab  
14:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-NetFx_en-us.cab  
15:    
16:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-PowerShell.cab  
17:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-PowerShell_en-us.cab  
18:    
19:  dism /image:c:\winpe\mount /add-package /packagepath:WinPE-DismCmdlets.cab  
20:  dism /image:c:\winpe\mount /add-package /packagepath:en-us\WinPE-DismCmdlets_en-us.cab  


Add customizations

Add a customized HTA menu (e.g., menu.hta in \windows\hta\) then add the below line to startnet.cmd in \windows\system32 to open it on startup:

%windir%\system32\mshta.exe %windir%\hta\menu.hta




Unmount boot.wim after committing changes

dism /unmount-wim /mountdir:c:\winpe\mount /commit


Prepare USB stick

(via either diskpart or Disk Management. The partition must be marked as Active.)

diskpart
list disk
select <disk number>
clean
create partition primary
format quick fs=ntfs label="WinPE"
assign letter=W
active


Example for finding the correct Disk number:

DISKPART> list disk

  Disk ###  Status         Size     Free     Dyn  Gpt
  --------  -------------  -------  -------  ---  ---
  Disk 0    Online          465 GB      0 B
  Disk 1    Online         7634 MB      0 B

DISKPART> sel disk 1

Disk 1 is now the selected disk.


Alternatively, prepare a USB HDD with multiple partitions:


diskpart
list disk
select <disk number>
clean
create partition primary
format quick fs=ntfs label="WinPE"
assign letter=W
active
select <disk number>
clean
create partition primary
format quick fs=ntfs label="Data"
assign letter=X


It's worth noting that UEFI only supports booting from FAT32 partitions.


Copy \media\ and its sub-directories to the root of the USB stick/drive:

robocopy /mir c:\winpe\media w:


Bulk File Extension Renamer - PowerShell

Here's a quick PowerShell script to change the extension on a group of matching files, posted chiefly to illustrate how much more concise PowerShell can be when compared to VBScript (I'll post an equivalent script shortly). The script below is still about twice the length of the a similar script I wrote in bash for *nix but it's certainly an improvement in both length and readability over VBS.

First up, grab the required parameters (old extension, new extension) from the command line. If either or both are missing, the user will be prompted to enter them.

Each parameter is set with three options -


[parameter(Mandatory = $true)] makes the parameter mandator.

ValidateNotNullorEmpty is called to do exactly what it says on the tin.
 

[string]$oldExt puts the value of the parameter into a string called $oldExt.

1:  param(  
2:       [parameter(Mandatory = $true)]  
3:       [ValidateNotNullOrEmpty()]  
4:       [string]$oldExt,  
5:       [parameter(Mandatory = $true)]  
6:       [ValidateNotNullOrEmpty()]  
7:       [string]$newExt  
8:  )
9:

Use Get-ChildItem to fill an array with files that have the old extension.

10:  $files = @(Get-ChildItem *.$oldExt)

If there is at least one filename in the array, loop through the array. Otherwise let the user know that there were no matches.

11:  if ($files.length -gt 0){
12:       foreach($file in $files){
13:              write-host "`nCurrent Name:" $file.Name
[ ... ]
32:  else {  
33:       write-host "No files with extension:" $oldExt`n -foregroundcolor "yellow"  
34:  }  


Create the new filename by combining the BaseName (original file sans extension) with the new extension (joined with a dot).

15:            $newName = $file.BaseName + "." + $newExt  
16:            write-host "New Name:" $newName  

Check if a file or directory with the new name already exists. Throw an error if so (in red, because *alarming*).
17:            if (Test-Path $newName) {  
18:                 write-host "Error: Can't rename" $file.Name "to" $newName "- a file with that name already exists`n" -foregroundcolor "red"  
19:            }  
20:            elseif ($file.Attributes -like "*ReadOnly*") {  
21:                 write-host "Cannot rename" $file.Name "- file is Read Only`n" -foregroundcolor "red"  
22:            }  
23:            elseif ($file.Attributes -like "*Directory*") {  
24:                 write-host "Not renaming" $file.Name "- it is a directory`n" -foregroundcolor "red"  
25:            } 

Otherwise rename the file and confirm with a happier green message.

26:            else {  
27:                 rename-item -path $file.Name -newname $newName  
28:                 write-host "File renamed`n" -foregroundcolor "green"  
29:            }  

And that's about that. Here's a screenshot of it in action:



Finally, for good measure, here's the complete listing:
1:  param(  
2:       [parameter(Mandatory = $true)]  
3:       [ValidateNotNullOrEmpty()]  
4:       [string]$oldExt,  
5:       [parameter(Mandatory = $true)]  
6:       [ValidateNotNullOrEmpty()]  
7:       [string]$newExt  
8:  )  
9:    
10:  $files = @(Get-ChildItem *.$oldExt)  
11:  if ($files.length -gt 0) {  
12:       write-host "`nFiles to be processed:" $files.length -foregroundcolor "yellow"  
13:       foreach($file in $files) {  
14:            write-host "`nCurrent Name:" $file.Name  
15:            $newName = $file.BaseName + "." + $newExt  
16:            write-host "New Name:" $newName  
17:            if (Test-Path $newName) {  
18:                 write-host "Error: Can't rename" $file.Name "to" $newName "- a file with that name already exists`n" -foregroundcolor "red"  
19:            }  
20:            elseif ($file.Attributes -like "*ReadOnly*") {  
21:                 write-host "Cannot rename" $file.Name "- file is Read Only`n" -foregroundcolor "red"  
22:            }  
23:            elseif ($file.Attributes -like "*Directory*") {  
24:                 write-host "Not renaming" $file.Name "- it is a directory`n" -foregroundcolor "red"  
25:            }  
26:            else {  
27:                 rename-item -path $file.Name -newname $newName  
28:                 write-host "File renamed`n" -foregroundcolor "green"  
29:            }  
30:       }  
31:  }  
32:  else {  
33:       write-host "No files with extension:" $oldExt`n -foregroundcolor "yellow"  
34:  }  

2014-05-08

Not a bad background for a morning


The view from my overnight digs made a very pleasant background for breakfast/coffee o'clock this morning. Loved the silence most of all - buried in the bush between Tathra and Bega.







Not that the interior was anything to sneeze at...




You just have to love off-peak rates that make places like this affordable for a simple business trip :)

Quote for the day

"I have so little that is fanciful or poetical about my own individu [sic] that I must trick out my dwelling with something fantastical otherwise the Coerulean Nymphs and swains will hold me nothing worth."

- Sir Walter Scott