Posts Tagged ‘Powershell’

Create a Virtual Drive (VHD) in Windows 7 using PowerShell

Wednesday, September 2nd, 2009

Creating a virtual drive in Windows 7 is a fairly simple task but I wanted to create a PowerShell script that would make things just a bit easier to perform.  This script contains a function (“Create-VhdDrive”) that only requires 2 parameters (path and filename) to create the new VHD disk but it does allow you to override all of the possibilities so you can create a fixed verses expandable, set the size, identify the drive letter to mount the new drive to, etc.

This script creates a VHD file, formats it and mounts it to your system.  It does not attempt to set up the drive for “Boot to VHD”.

The script requires that you are an administrator and when it runs the “Are you sure you want to do this” dialog will appear.

As the script runs a dialog box may appear like the one below:

Need to Format

Cancel out of that window since the script does format the drive for you.

When the script is done, the auto-play dialog will appear. 

AutoPlay

In fact, it may appear before you have a chance to cancel out of the format dialog.  This may happen if you create an expandable drive since an expandable drive does not require setting aside all of the required space ahead of time.

Once that dialog appears, the drive has been created and is ready for use.

Here is the script (there is an example of a call to the main function (“Create-VhdDrive”) at the bottom of the script):

# CreateVhd.ps1
#
# Summary: This script will create and attach a Windows 7 Virtual Hard Drive (VHD)
# The Virtual Hard Drive is essentially a file that can be attached to a Windows 7
# machine in such a way that it appears and acts just as any other drive on your 
# computer.  Benefits include attaching and unattaching at will, being able to back
# up the drive simply by copying a single file, ability to create fixed or expandable 
# drives, ability to create any size drive up to multiple terabytes (assuming your 
# physical hard drive has the space), etc.

# function: Check-AvailableDrive
# This function will check to see if a drive letter is available. This is important
# when trying to assign and attach a drive.  The drive letter must not already be 
# in use.  
function Check-AvailableDrive {
    param ( $driveLetterToSearch )

    foreach( $driveFound in [System.IO.DriveInfo]::GetDrives()) {
        if ($driveLetterToSearch + ":`\" -eq $driveFound.Name) { return $false } # the drive letter is in use?
    }

    # Drive letter is available
    return $true
}

# function: Create-VhdDrive
# This function generates a script and passes that script to the DiskPart utility
# to generate a formatted VHD drive at a specified location.
# $vdiskPath (Required) is the drive:\folder location of the new VHD drive file
# $vdiskFileName (required) is the name of the new VHD file to create (Note: the file does not have to 
#   end with ".vhd" for it to work but it is the standard.
# The rest of the parameters are optional and have default values
function Create-VhdDrive {
    param ( $vdiskPath = $(Throw "vdiskPath was not specified"),
            $vdiskFileName = $(Throw "vdiskFileName was not specified"),
            $vdiskMaximumSize = 20000, $vdiskType = "EXPANDABLE",
            $vdiskDriveLetter = "", $vdiskVolumeName = "NewVHD",
            $vdiskScriptFileName = "CreateVhd.scr" )

    # Validate the drive letter
    if ($vdiskDriveLetter.length -eq 0)
    {
        $driveAssign = ""
    } else {
        if ($vdiskDriveLetter.length -ne 1) {
            Throw "Error: the variable `"driveLetter`" must be a single letter only."
        } else {
            if ((Check-AvailableDrive $vdiskDriveLetter) -eq $false) {
                $errorResult = "Cannot create drive $vdiskDriveLetter - it already exists."
                Throw $errorResult
            }

            $driveAssign = "LETTER=$vdiskDriveLetter"
        }
    }

    # Validate the disk type to be FIXED or EXPANDABLE
    if (($vdiskType -ne "FIXED") -and ($vdiskType -ne "EXPANDABLE")) {
        Throw "Error: vdiskType must be `"FIXED`" or `"EXPANDABLE`""
    }

    # Validate that the destination path for the new file does indeed exists
    if (![System.IO.Directory]::Exists($vdiskPath)) {
        Throw "Error: the destination folder does not exist - $vdiskPath"
    }

    # Create the full path for the new file
    $vdiskFullFilePath = [System.IO.Path]::Combine($vdiskPath, $vdiskFileName)

    # Generate the text for the script
    $scriptLineToWrite = "CREATE VDISK FILE=`"$vdiskFullFilePath`" MAXIMUM=$vdiskMaximumSize TYPE=$vdiskType`n"
    $scriptLineToWrite = $scriptLineToWrite + "SELECT VDISK FILE=`"$vdiskFullFilePath`"`n"
    $scriptLineToWrite = $scriptLineToWrite + "ATTACH VDISK`n"
    $scriptLineToWrite = $scriptLineToWrite + "CREATE PARTITION PRIMARY`n"
    $scriptLineToWrite = $scriptLineToWrite + "ASSIGN $driveAssign`n"
    $scriptLineToWrite = $scriptLineToWrite + "FORMAT QUICK LABEL=$vdiskVolumeName"

    # Write the script out to disk so it can be passed into DiskPart
    $scriptPath = [System.IO.Path]::Combine($vdiskPath, $vdiskScriptFileName)
    $scriptLineToWrite | out-file -encoding ASCII $scriptPath

    # Create the VHD Disk File
    diskpart /s $scriptPath
}

# Example function call to create a 30GB expandable file:
Create-VhdDrive -vdiskPath "d:\bootfromvhd" -vdiskFileName "MyNewVhdDisk2.vhd" -vdiskMaximumSize "10000" -vdiskVolumeName "MyVhdDrive01"

Use Powershell and Excel to Manage Text Resource Files

Sunday, August 9th, 2009

Recently I had to create a demo for a presentation where I could show how we created automated functional testing for a multi-language WPF application.  I have lead testing teams that developed automated tests for a WPF app but unfortunately, I didn’t have much experience in the actual creation of a multi-language WPF GUI so I worked to figure it out.  I did some research using Bing and discovered that there isn’t a definitive mechanism for providing multi-language capability in a WPF application.  I decided that I would use static WPF binding to the text resource files for the non-dynamic text.  It is easy to do and was very effective for my presentation.

So, I created my app as well as the language-based resource files (English as the default, Spanish and even Japanese).  I left the resource files empty.  I didn’t fill them in because I wanted to manage the text in a single place and use Excel to create my “single source of the truth”.  With Excel I can use the database-like table structure to manage all of the translations.  It would then be the source for all of my automated tests.  As long as the translations are correct, I can assert the Excel values against the application output.

Excel Language Table

Since I was putting all of the text into Excel, I still needed to be able to copy all of the key/value pairs into the resource files.  Laziness (and the need for accuracy) kept me from thinking that I would do all of that copying manually so Powershell to the rescue.  I created a script that would walk through all of the entries in my Excel spreadsheet and insert the appropriate key/value pairs into the associated language resource file.

Here is the project including the resource file naming conventions.  Resources.resx is the “default” language file and in this app it is English.  Default means that if there is a failure to locate an entry in the specified culture/language then an attempt to find an entry in the default file will be performed.  If it is found then it will be used.

Properties-Resource Files 

I broke the script down into a couple major steps:

  1. Get the data from Excel using standard database connections and place the data in a dataset for later use
  2. Loop on each language that I specify in the script
  3. Generate the resource file (resx) for each language

Here is the Powershell code to extract the Excel data into a dataset table:

# function: Get-ExcelLanguageData

# Extract the ID and text values from the spreadsheet

# This data will be directly used to create the region

# specific resource files later

function Get-ExcelLanguageData {

    param( [string] $excelFilePath )

    

    # Use OleDb to open the Excel spreadsheet

    $selectStatement = "Select * from [$sheetName] Where TextId <> `"`""

 

    $connection = New-Object Data.OleDb.OleDbConnection "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$excelFilePath`";Extended Properties=`"Excel 12.0 Xml;HDR=YES`";"

    $command = New-Object Data.OleDb.OleDbCommand $selectStatement, $connection

        

    $connection.Open()

        

    $dataAdapter = New-Object Data.OleDb.OleDbDataAdapter $command

        

    $dataSet = New-Object Data.DataSet

        

    [Void] $dataAdapter.Fill($dataSet)

        

    $connection.Close()

        

    return $dataSet

}

It takes, as input, the full path of the Excel spreadsheet, opens it using an OleDB connection string and fills a dataset with the data from the specified spreadsheet.

Next is the Powershell code to generate a resource file.  It deletes any current entries within the resx file before creating all new entries with the data from Excel.  This code assumes that the resx files already exist in the application.

# function Create-ResxFile

# Use the region-specific language data (the dataset created from Excel) 

#  and modify the EXISTING resource files from my C# project.

function Create-ResxFile {

    param ( [string] $resxFilePath, [Data.DataSet] $excelData, [string] $languageColumn )

    

    $xmlDoc = new-object "System.Xml.XmlDocument"

 

    $xmlDoc.Load($resxFilePath)

  

    $dataNodeList = $xmlDoc.selectNodes("/root/data")

 

    # Remove any existing data nodes since they will all be replaced with the data from excel

    foreach($dataNode in $dataNodeList)

    {

        $dataNode.parentNode.removeChild($dataNode);

    }

 

    # Create each of the "data" elements in XML - this is where the 

    #  text data resides with the resource key

    foreach ($row in $excelData.Tables[0])

    {

        $dataElem = $xmlDoc.CreateElement("data")

        $nameAttr = $xmlDoc.CreateAttribute("name")

 

        $preserveAttr = $xmlDoc.CreateAttribute("xml:space")

        $preserveAttr.Value = "preserve"

 

        $nameAttr.Value = $row['TextId']

 

        $dataElem.Attributes.Append($nameAttr)

        $dataElem.Attributes.Append($preserveAttr)

        $dataElem.InnerXml = $row[$languageColumn]

 

        $rootNode = $xmlDoc.selectSingleNode("/root")

 

        $rootNode.AppendChild($dataElem);

    }

 

    # Save the resource file

    $xmlDoc.Save($resxFilePath)

}

 

The only part left is to loop through each of the languages and fill the resource files.  The code below shows how I performed that step.  I provide the source location information for the Excel file, the name of the spreadsheet and the language/culture information.  The “$resxRegions” Powershell variable holds the culture and column name information.  So, for a given language/culture it is paired with where to find the translated data – in other words, what column in the spreadsheet contains that language data.

# Root location of the spreadsheet and resx resource files

$projectPath = "D:\_App Dev\TechReady9_DEV320\TechReady9_DEV320"

# Filename of the Excel Translation spreadsheet

$excelDataFile = "TextResourceTranslation.xlsx"

# The name of the Excel sheet with the translation data in it

$sheetName = "TextTranslation`$"    

 

# Array of arrays: (region, Excel column name)

# For this script, the region is also used as the resource file (resx) filename

# Note: if the region is left blank ("") then it will become the default resource file

$resxRegions = @(("", "enUS"), ("es-ES", "esES"), ("ja-JP", "jaJP"))

 

# This is the subfolder where the resx files are to be found/stored

$resxSubfolder = "Properties"

 

# This creates the full path for the Excel translation data file

[string] $excelFullPath = [System.IO.Path]::Combine($projectPath, $excelDataFile)

 

# Get the data from Excel and store in a dataset

[Data.DataSet] $excelData = Get-ExcelLanguageData $excelFullPath

 

# Loop through the regions and build the resx resource files

#  extracting the data from the Excel spreadsheet 

#  and modifying/adding to the xml in the resource file 

foreach($regionData in $resxRegions) {

 

    [string] $resxFilename = ""

 

    if ($regionData[0] -eq "") {

        $resxFilename = "Resources.resx"

    } else {

        $resxFilename = "Resources.$($regionData[0]).resx"

    }

    

    # Create the full path to the resource file

    [string] $resxFullPath = [System.IO.Path]::Combine($projectPath, $resxSubfolder)

    $resxFullPath = [System.IO.Path]::Combine($resxFullPath, $resxFilename)

    

    # Use the dataset to generate the resource 

    Create-ResxFile $resxFullPath $excelData $regionData[1]

}

 

Here is the full script:

# ResourceGenerator.ps1

#

# Summary: Script that will extract region-specific text data from an Excel

# spreadsheet and update resource files.  This allows you to create a master

# spreadsheet that contains all of your translated application text data in one

# central location.  The data in the resx files are then slaves to the 

# spreadsheet.  This way you have one source of the "true" text data within

# a multi-language application.

 

# NOTE: This script will not work in 64bit Powershell because of the lack of a

#  64bit OleDb driver for Excel.  It will work on (in fact was developed on) a 

#  64bit machine - you just have to run 32bit Powershell.

#

# Steps to make this work in your application are toward the bottom of this script

 

# function: Get-ExcelLanguageData

# Extract the ID and text values from the spreadsheet

# This data will be directly used to create the region

# specific resource files later

function Get-ExcelLanguageData {

    param( [string] $excelFilePath )

    

    # Use OleDb to open the Excel spreadsheet

    $selectStatement = "Select * from [$sheetName] Where TextId <> `"`""

 

    $connection = New-Object Data.OleDb.OleDbConnection "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=`"$excelFilePath`";Extended Properties=`"Excel 12.0 Xml;HDR=YES`";"

    $command = New-Object Data.OleDb.OleDbCommand $selectStatement, $connection

        

    $connection.Open()

        

    $dataAdapter = New-Object Data.OleDb.OleDbDataAdapter $command

        

    $dataSet = New-Object Data.DataSet

        

    [Void] $dataAdapter.Fill($dataSet)

        

    $connection.Close()

        

    return $dataSet

}

 

# function Create-ResxFile

# Use the region-specific language data (the dataset created from Excel) 

#  and modify the EXISTING resource files from my C# project.

function Create-ResxFile {

    param ( [string] $resxFilePath, [Data.DataSet] $excelData, [string] $languageColumn )

    

    $xmlDoc = new-object "System.Xml.XmlDocument"

 

    $xmlDoc.Load($resxFilePath)

  

    $dataNodeList = $xmlDoc.selectNodes("/root/data")

 

    # Remove any existing data nodes since they will all be replaced with the data from excel

    foreach($dataNode in $dataNodeList)

    {

        $dataNode.parentNode.removeChild($dataNode);

    }

 

    # Create each of the "data" elements in XML - this is where the 

    #  text data resides with the resource key

    foreach ($row in $excelData.Tables[0])

    {

        $dataElem = $xmlDoc.CreateElement("data")

        $nameAttr = $xmlDoc.CreateAttribute("name")

 

        $preserveAttr = $xmlDoc.CreateAttribute("xml:space")

        $preserveAttr.Value = "preserve"

 

        $nameAttr.Value = $row['TextId']

 

        $dataElem.Attributes.Append($nameAttr)

        $dataElem.Attributes.Append($preserveAttr)

        $dataElem.InnerXml = $row[$languageColumn]

 

        $rootNode = $xmlDoc.selectSingleNode("/root")

 

        $rootNode.AppendChild($dataElem);

    }

 

    # Save the resource file

    $xmlDoc.Save($resxFilePath)

}

 

 

# Steps to make this work for your application"

#

# 1: Review the "TextResourceTranslation.xlsx" spreadsheet in this project to 

#     get a good feeling for how this script works with the column/row values

#     within it.

# 2: Create your own translation spreadsheet or modify the one provided

# 3: Substitute location path values below to match your project location

# 4: Substitute appropriate filenames

# 5: Substitute the name of the spreadsheet ($sheetName - this is the name from

#     the Excel workbook. In other words, the name on the tab at the bottom of

#     Excel.  In a new spreadsheet this defaults to "Sheet1".

# 6: Modify the $resxRegions array values.  The first value is the name 

#     of the resource (resx) file.  The second value is the column name of the 

#     appropriate language text within the spreadsheet.

 

 

 

# Root location of the spreadsheet and resx resource files

$projectPath = "D:\_App Dev\TechReady9_DEV320\TechReady9_DEV320"

# Filename of the Excel Translation spreadsheet

$excelDataFile = "TextResourceTranslation.xlsx"

# The name of the Excel sheet with the translation data in it

$sheetName = "TextTranslation`$"    

 

# Array of arrays: (region, Excel column name)

# For this script, the region is also used as the resource file (resx) filename

# Note: if the region is left blank ("") then it will become the default resource file

$resxRegions = @(("", "enUS"), ("es-ES", "esES"), ("ja-JP", "jaJP"))

 

# This is the subfolder where the resx files are to be found/stored

$resxSubfolder = "Properties"

 

# This creates the full path for the Excel translation data file

[string] $excelFullPath = [System.IO.Path]::Combine($projectPath, $excelDataFile)

 

# Get the data from Excel and store in a dataset

[Data.DataSet] $excelData = Get-ExcelLanguageData $excelFullPath

 

# Loop through the regions and build the resx resource files

#  extracting the data from the Excel spreadsheet 

#  and modifying/adding to the xml in the resource file 

foreach($regionData in $resxRegions) {

 

    [string] $resxFilename = ""

 

    if ($regionData[0] -eq "") {

        $resxFilename = "Resources.resx"

    } else {

        $resxFilename = "Resources.$($regionData[0]).resx"

    }

    

    # Create the full path to the resource file

    [string] $resxFullPath = [System.IO.Path]::Combine($projectPath, $resxSubfolder)

    $resxFullPath = [System.IO.Path]::Combine($resxFullPath, $resxFilename)

    

    # Use the dataset to generate the resource 

    Create-ResxFile $resxFullPath $excelData $regionData[1]

}

 

There are additional instructions included within the Powershell file showing what variables to modify to point the script towards your spreadsheet data and Visual Studio project.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes