Quantcast
Channel: Scripting Blog
Viewing all 2129 articles
Browse latest View live

PowerTip: Use PowerShell to Mount ISO

$
0
0

Summary: Easily mount ISO files by using Windows PowerShell.

Hey, Scripting Guy! Question How can I use Windows PowerShell to mount ISO files in Windows 8?

Hey, Scripting Guy! Answer Use the Mount-Disk cmdlet and specify the exact path of the ISO file, for example:

Mount-DiskImage C:\Media\Filename.ISO


Update Offline Virtual Machine with PowerShell and WSUS Offline Update: Part 2

$
0
0

Summary: Microsoft PowerShell MVP, Sean Kearney, talks about updating an offline virtual machine with Windows PowerShell and WSUS Offline Update.

Honorary Scripting Guy, Sean Kearney, here. Yesterday, I introduced you to a tool you can use to updating offline virtual machines: Update Offline Virtual Machine with PowerShell and WSUS Offline Update: Part 1. Today, we'll learn how to conclude our process.

Within the folder structure is a folder called Client. This is the structure we need to expose to our offline virtual machines. The process we’re going to follow will be very simple:

  • Create a VHD file structure
  • Mount the VHD file
  • Partition and format the VHD file
  • Copy the folder structure to the VHD file
  • Dismount the VHD file

The following process is repeated for each virtual machine you supply:

  • Get a list of virtual machines that are off.
  • Attach the VHD file to the virtual machines, one at a time.
  • Inject a setting to launch a script into the virtual machine Registry.
  • Temporarily adjust the Registry to automatically log in as a user with local Admin rights.
  • Temporarily disable the User Account Control (UAC).
  • Temporarily change the Windows PowerShell execution policy.
  • Inject a small script to identify our VHD and launch the update script.
  • Power up the virtual machine and wait for it to update, then shut down automatically.
  • Remove the WSUS Offline Updates VHD.
  • Power up the virtual machine and leave it running for about two hours to allow it to process the updates.
  • Power down after the allocated time period.

We do this until we’ve run out of virtual machines that we want to patch...

To get started, we make that new VHD file and format it as one giant NTFS file system. For a detailed explanation of the process that follows, please refer to this most excellent post by Ed Wilson: Use PowerShell to Initialize Raw Disks and to Partition and Format Volumes.

# Create the VHD file

$VHDPath=’C:\WsusOffline\Updates.VHDX’

New-VHD $VHDPath -SizeBytes 20GB -Dynamic

# Attach the VHD to the Windows File system

Mount-VHD $VHDPath

# Partition the VHD

$VHD=Get-VHD $VHDPath | Get-Disk

$Drive=$VHD | Initialize-Disk -PartitionStyle MBR –PassThru |

New-Partition –AssignDriveLetter –UseMaximumSize |

Format-Volume –FileSystem NTFS –NewFileSystemLabel ‘WSUSOffline’ –Confirm:$false

You might notice a coulple differences in this code when compared to Ed’s post. First, instead of filtering for Raw file systems, I have told Get-Disk to work explicitly with the file I’m working with.

The second key piece is that I am capturing the results of the formatting in a variable called $Drive. In this manner, I can programmatically grab the drive letter that is assigned to the newly formatted VHD file. (Remember, we need to copy the client structure from Wsusoffline into this VHD file.)

Now that the disk has been prepared, naturally, we can just copy the folder structure with a little Windows PowerShell:

Copy-Item -path 'C:\wsusoffline\client' -Recurse -destination (“$($Drive.DriveLetter):\client”) -Force

Now that the files are in the VHD file, we need to dismount it so we can use it within the virtual machines:

Dismount-VHD $VHDPath

Before all of this, we need a few commands in Windows PowerShell so our script will:

  • Find the attached disk with the name WsusOffline.
  • Obtain its drive letter.
  • Launch the command under the folder named update.cmd.

All this is simply this little Windows PowerShell script:

$Drive=Get-Volume | where { $_.FileSystemLabel -eq 'WSUSOffline' }

$Appname="$($Drive.Driveletter):\client\cmd\doupdate.cmd"

invoke-expression $appname

stop-computer

Now we access Hyper-V and get a list of all virtual machines that are presently offline with this simple loop:

$VMlist=Get-VM | Where { $_.State –eq 'Off' }

Foreach ($VM in $VMlist)

{

}

The contents of the loop will look like the following. We will be running under the presumption that the first hard disk is the boot disk.

# Get virtual machine Name

$VMName=$VM.VMName

# Get the location of the VHD

$VMDiskPath=(Get-VM $VMName | Get-VMHardDiskDrive).Path

# Mount the VHD and get it's Drive letter

Mount-DiskImage $VMDiskPath

$DriveLetter=((Get-DiskImage $VMDiskpath | get-disk | get-partition | Where { $_.Type -eq 'Basic' }).DriveLetter)+":"

# Then create a folder to hold our autolaunch Script

$ScriptFolder=$DriveLetter+"\ProgramData\Scripts\"

New-Item $ScriptFolder -ItemType Directory -force

# Copy over our little PowerShell script to trigger to update media

Copy-Item "C:\Wsusoffline\UpdatePC.PS1" $ScriptFolder

Now for the fun part...

We're going to connect to the remote software registry and adjust the settings. This involves capturing the original settings. We change them for the automatic log in and to disable the UAC. When the process is done, we revert them back:

# Connect to the Registry remotely and grab some settings

$RemoteReg=$DriveLetter+"\Windows\System32\config\Software"

# Load the remote file registry

REG LOAD 'HKLM\REMOTEPC' $RemoteReg

# Capture the original properties for Autologin

$Key='HKLM:\REMOTEPC\Microsoft\Windows NT\CurrentVersion\Winlogon'

$Admin=(Get-ItemProperty $KEY -name AutoAdminLogon -ErrorAction SilentlyContinue).AutoAdminLogon

$Domain=(Get-ItemProperty $KEY -name DefaultDomainName -ErrorAction SilentlyContinue).DefaultDomainName

$Username=(Get-ItemProperty $KEY -name DefaultUserName -ErrorAction SilentlyContinue).DefaultUserName

$Password=(Get-ItemProperty $KEY -name DefaultPassword -ErrorAction SilentlyContinue).DefaultPassword

# Then pass in the new values which presume all

# Offline VMs have the same ID and Password for the local

# Admin account.

Set-ItemProperty $KEY -name AutoAdminLogon –value 1 -force

Set-ItemProperty $KEY -name DefaultDomainName -value 'localhost' -force

Set-ItemProperty $KEY -name DefaultUserName -Value 'Administrator' -force

Set-ItemProperty $KEY -name DefaultPassword -Value 'P@ssw0rd' -force

# We now do the same for UAC

# Capture the old settings first

$Key='HKLM:\REMOTEPC\Microsoft\Windows\CurrentVersion\Policies\System'

$ConsentAdmin=(Get-ItemProperty $KEY -name ConsentPromptBehaviorAdmin -ErrorAction SilentlyContinue). ConsentPromptBehaviorAdmin

$ConsentUser=(Get-ItemProperty $KEY -name ConsentPromptBehaviorUser -ErrorAction SilentlyContinue). ConsentPromptBehaviorUser

$LUA=(Get-ItemProperty $KEY -name EnableLUA -ErrorAction SilentlyContinue).EnableLUA

$SecureDesk=(Get-ItemProperty $KEY -name PromptOnSecureDesktop -ErrorAction SilentlyContinue).PromptOnSecureDesktop

# And now we (Quick everybody HIDE!)

# Temporarily turn off UAC!

Set-ItemProperty $KEY -name ConsentPromptBehaviorAdmin –Value 0

Set-ItemProperty $KEY -name ConsentPromptBehaviorUser –Value 0

Set-ItemProperty $KEY -name EnableLUA –Value 1

Set-ItemProperty $KEY -name PromptOnSecureDesktop –Value 0

# Capture the Current Execution Policy for PowerShell

$Key='HKLM:\REMOTEPC\Microsoft\Powershell\1\Shellids\Microsoft.Powershell'

$PowershellPolicy=(Get-ItemProperty $KEY -name ExecutionPolicy -ErrorAction SilentlyContinue). ExecutionPolicy

# Set the Execution Policy to Bypass

Set-ItemProperty $KEY -name ExecutionPolicy –Value 'Bypass'

# Finally we tell the remote computer at First run to execute the UpdatePC script.

NEW-ITEMPROPERTY "HKLM:\REMOTEPC\Microsoft\Windows\CurrentVersion\Run\" -Name "PoshStart" -Value "`"C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe`" -file C:\ProgramData\Scripts\UpdatePC.PS1"

# Then disconnect the remote registry

REG UNLOAD 'HKLM\REMOTEPC'

# And now dismount the Disk

dismount-diskimage $VMDiskPath

We need to attach the VHD to the virtual machine. We'll do this by adding in a SCSI controller to our virtual machine and then add the VHD to that SCSI controller. We are only going to add in a new SCSI controller if none already exists.

# Check for total SCSI Controllers

$TotalSCSI=(GET-VMScsiController -vmname $VMName).count

Get-VM –Vmname $VMName | Add-VMScsiController

# Attach the Updates VHDX file

Get-VM –Vmname $VMName | Add-VMHarddiskDrive –Controllertype SCSI –Path $VHDPath -ControllerNumber ($TotalScsi)

Now that we've done all the heavy lifting, here's the easy part. Start the virtual machine, wait for its first shutdown so we can detach the VHD, and then start it one final time to allow the updates to apply:

# Start the virtual machine

Start-VM -vmname $VMName

# A slight for faster machines to ensure the virtual machine

# State is passed back properly first

Start-Sleep -seconds 60

# Wait until the machine has pulled in the updates

# and Stops when it's done.

Do { $status=(Get-VM –vmname $VMname).state } until ($status –match 'Off')

Next we will detach the VHD file from the virtual machine to prevent it from continually trying to load the updates:

# Detach Updates VHD from virtual machine

Get-VM –Vmname $VMName | Remove-VMHarddiskDrive –Controllertype SCSI –Path $VHDPath

If(!$TotalSCSI) { Remove-VMScsiController -vmname $VMname -ControllerNumber 0 }

Now we ask Windows PowerShell to twiddle its thumbs (or maybe thumb through the TV to find some more Doctor Who) to wait for this machine to update. In this case, we'll take a nap for a couple of hours. If your virtual machines are a bit more up-to-date, you can crank this down as you need.

Start-VM –vmname $VMName

Start-Sleep –seconds 7200; # 60 seconds in a minute, 60 minutes in an hour times 2

Stop-VM –vmname $VMName

After about two hours of rebooting and operating, all of the updates should have been applied, right? You HAVE been keeping your virtual machines up-to-date, right?

Our next steps after finishing this round of updates are:

  • Mount the virtual machine VHD file
  • Restore all the settings in the virtual machine registry
  • Remove the Windows PowerShell script we placed on the virtual machine
  • Detach the VHD file

Here's our script:

# Reconnect VHD to Host so we can clean the registry back up

Mount-DiskImage $VMDiskPath

$DriveLetter=((Get-DiskImage $VMDiskpath | get-disk | get-partition | Where { $_.Type -eq 'Basic' }).DriveLetter)+":"

# Remove that PowerShell script

$ScriptFolder=$DriveLetter+"\ProgramData\Scripts\"

Remove-Item $ScriptFolder -ItemType Directory –recurse -force

# Connect to the Registry remotely and grab some settings

$RemoteReg=$DriveLetter+"\Windows\System32\config\Software"

# Load the remote file registry

REG LOAD 'HKLM\REMOTEPC' $RemoteReg

# Restore the original properties for Autologin

$Key='HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon'

Set-ItemProperty $KEY -name AutoAdminLogon –value $Admin -force

Set-ItemProperty $KEY -name DefaultDomainName -value $Domain -force

Set-ItemProperty $KEY -name DefaultUserName -Value $Username -force

Set-ItemProperty $KEY -name DefaultPassword -Value $Password -force

# We now do the same for UAC

# Restore the old settings first

$Key='HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Policies\System'

Set-ItemProperty $KEY -name ConsentPromptBehaviorAdmin –Value $ConsentAdmin

Set-ItemProperty $KEY -name ConsentPromptBehaviorUser –Value $ConsentUser

Set-ItemProperty $KEY -name EnableLUA –Value $SilentlyContinue

Set-ItemProperty $KEY -name PromptOnSecureDesktop –Value $SecureDesk

# Restore the original Execution Policy

$Key='HKLM:\REMOTEPC\Microsoft\Powershell\1\Shellids\Microsoft.Powershell'

Set-ItemProperty $KEY -name ExecutionPolicy –Value $PowershellPolicy

# Then Remove the autostart for the Script

SET-ITEMPROPERTY "HKLM:\REMOTEPC\Microsoft\Windows\CurrentVersion\Run\" -Name "PoshStart" -Value $NULL

# Then disconnect the remote registry

REG UNLOAD 'HKLM\REMOTEPC'

# And now dismount the Disk

dismount-diskimage $VMDiskPath

There you have it. The interesting thing to consider is that you require no network access from any of these virtual machines for this to operate. If you play more with WSUS Offline Update, you'll see that there are settings in the .ini files to automate more of its update process.

Of course, depending on how far out-of-date those virtual machines are, you may have to repeat the process a few times. But because this is automatable, you can sit back with a nice glass of iced tea and watch back-to-back episodes of Serenity while it happens.

Keep in mind that the media we created today only contains media for Windows 8.1 and Windows Server 2008 R2. You can expand it to contain all of the currently supported operating systems and Microsoft Office suites. All you need to do is check more boxes.

This was a long read today; but of course, what a volume of information and neat tricks in Windows PowerShell we got to play with today! Think about it. Turning on a virtual machine and programming it to do something without network access—and then undoing all of that in the end. 

We invite you to follow us on Twitter and Facebook. If you have any questions, send email to scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow.

Sean Kearney, Microsoft PowerShell MVP and Honorary Scripting Guy

PowerTip: Shut Down Virtual Machine with PowerShell

$
0
0

Summary: Learn how to shut down a virtual machine by using Windows PowerShell.

Hey, Scripting Guy! Question How can I use Windows PowerShell to perform an orderly shutdown of a virtual machine?

Hey, Scripting Guy! Answer When the Stop-VM cmdlet is used with no parameters, it conducts an orderly shutdown.
           Here is an example:

Stop-VM –name C1

PSImaging Part 1: Test-Image

$
0
0

Summary: Guest blogger, Ben Vierck, talks about using Windows PowerShell to determine if a file is an image.

Microsoft Scripting Guy, Ed Wilson, is here. I am happy to introduce a new guest blogger here at the Hey, Scripting Guy! Blog: Ben Vierck. Ben has been around for a while, using and supporting Windows PowerShell, and he certainly is not a noob by any stretch of the imagination. I had not previously talked to him about writing a guest blog, and then Teresa mentioned it to me. What would I do without the Scripting Wife? Let’s hope I don’t have to find out!

Ben is presenting a three-part series about images. Now here’s Ben...

The process of ingesting paper documents into our systems and managing the lifecycle of that paper is fragile. Legacy software systems can do that work—systems with core code that hasn't been touched in over ten years and with architectures that weren't designed to accommodate the modern world of the cloud and IoT. The Windows community—especially the corner of that community that is obsessed with automation, Windows PowerShell, and Azure—have a lot to offer this aging industry. Let's go!

It all starts with a piece of paper, typically delivered by mail, leafed in a document, which is one in a batch of documents. These batches are placed on scanners (sometimes as large as a room) and digitized. It's at this point that the capture process begins.  

Fundamental to the capture process is the ability to manipulate the digital artifacts created during the scanning process. Before we jump in and begin feeding our pipelines like this:

dir *.tiff, *.jpg, *.png, *.pdf

...let's ask the question, "What is an image and how do we know that a file is one?" We need a Test-Image cmdlet! You might suggest that we compare the file extension against a set of known image file extensions:

function Test-Image {

[CmdletBinding()]

param(

   [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]

        [ValidateNotNullOrEmpty()]

        [Alias('PSPath')]

        $Path

PROCESS {

        $knownImageExtensions = @( ".jpg", ".bmp", ".gif", ".tif", ".pdf", ".png" )

        $extension = [System.IO.Path]::GetExtension($Path.FullName)

        return $knownImageExtensions -contains $extension.ToLower()

}

}

Let's try that. Oh no...

Image of command output

The first time out, it's failed to identify a .tiff. Sure, I could go back and modify the $knownImageExtensions, but perhaps it would be better to come up with an algorithm that is more resilient to the whims of the users on my systems, so that they can arbitrarily name the image files with whatever extension they'd like. Let's begin cracking open these image files in a binary editor to see what they're made of. In this screenshot, I've opened one of my test TIFF files in the Visual Studio Binary Editor:

Image of file

After opening several TIFFs in a binary editor, I notice that all of those files share the same first 3 bits: 49 49 2A. A quick look at the TIFF Specification confirms the discovery. Similarly, other formats have distinct signatures. Here is a table of some well-known image file signatures:

 Type

 Bit 1

 Bit 2

 Bit 3

 Bit 4

 Bit 5

 Bit 6

 Bit 7

 Bit 8

 jpg

 FF

 D8

 

 

 

 

 

 

 bmp

 42

 4D

 

 

 

 

 

 

 gif

 47

 49

 46

 

 

 

 

 

 tif

 49

 49

 2A

 

 

 

 

 

 pdf

 25

 50

 44

 46

 

 

 

 

 png

 89

 50

 4E

 47

 0D

 0A

 1A

 0A

The algorithm to search for these patterns almost writes itself. I'll make a reference table for the known image header bit signatures:

$knownHeaders = @{

    jpg = @( "FF", "D8" );

    bmp = @( "42", "4D" );

    gif = @( "47", "49", "46" );

    tif = @( "49", "49", "2A" );

    pdf = @( "25", "50", "44", "46" );

    png = @( "89", "50", "4E", "47", "0D", "0A", "1A", "0A" );

}

Now read the first 8 bits of a file:

$bytes = Get-Content $path -Encoding Byte -ReadCount 1 -TotalCount 8

Convert the read bits into the same format as our reference arrays:

$fileHeader = ($bytes | select -first $knownHeaders['tif'].Length | % { $_.ToString("X2") })

Compare the file byte array to the reference arrays:

Compare-Object -ReferenceObject $knownHeaders['tif'] -DifferenceObject $fileHeader

If there's a match, the file is an image, regardless of what its file extension says. If not, it's not. Putting it all together, the script looks like this:

function Test-Image {

    [CmdletBinding()]

    [OutputType([System.Boolean])]

    param(

        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]

        [ValidateNotNullOrEmpty()]

        [Alias('PSPath')]

        [string] $Path

    )

    PROCESS {

        $knownHeaders = @{

            jpg = @( "FF", "D8" );

            bmp = @( "42", "4D" );

            gif = @( "47", "49", "46" );

            tif = @( "49", "49", "2A" );

            png = @( "89", "50", "4E", "47", "0D", "0A", "1A", "0A" );

            pdf = @( "25", "50", "44", "46" );

        }

        # coerce relative paths from the pipeline into full paths

        if($_ -ne $null) {

            $Path = $_.FullName

        }

         # read in the first 8 bits

        $bytes = Get-Content -LiteralPath $Path -Encoding Byte -ReadCount 1 -TotalCount 8 -ErrorAction Ignore

         $retval = $false

        foreach($key in $knownHeaders.Keys) {

             # make the file header data the same length and format as the known header

            $fileHeader = $bytes |

                Select-Object -First $knownHeaders[$key].Length |

                ForEach-Object { $_.ToString("X2") }

            if($fileHeader.Length -eq 0) {

                continue

            }

             # compare the two headers

            $diff = Compare-Object -ReferenceObject $knownHeaders[$key] -DifferenceObject $fileHeader

            if(($diff | Measure-Object).Count -eq 0) {

                $retval = $true

            }

        }

        return $retval

    }

}

That's functional, easy-to-use, and tolerant of variable file extensions. Here's the output:

Image of command output

Next up in the series…

We've got an image of a document. How do we find out what kind of document it is?

Note  This script and the others included in this series are maintained on GitHub: Positronic-IO/PSImaging.

~Ben

Thanks, Ben. Be sure to come back tomorrow for Part 2 of this series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Read .exe Magic Number with PowerShell

$
0
0

Summary: Learn how to use Windows PowerShell to see an .exe magic number.

Hey, Scripting Guy! Question How can I use Windows PowerShell to see the "magic number" associated with an executable file in Windows?

Hey, Scripting Guy! Answer The term magic number refers to the bytes that occur at the beginning of every file and identify the format of file.
           Use this command to read a Windows executable file header with Windows PowerShell:

 [char[]](gc $env:windir\notepad.exe -Encoding Byte -ReadCount 1 -TotalCount 2)

PSImaging Part 2: Export-Text from Images

$
0
0

Summary: Guest blogger, Ben Vierck, talks about using Windows PowerShell to export text from an image.

Microsoft Scripting Guy, Ed Wilson, is here. Welcome back guest blogger Ben Vierck, for Part 2 of PSImaging. Read Part 1 before diving into today’s post: PSImaging Part 1: Test-Image.

Now, here’s Ben...

In first blog post of this series, we wrote the Windows PowerShell function Test-Image to definitively detect whether a file is a known image type by analyzing the first 8 bits of its header. In this post, we're going to write a Windows PowerShell command with a cmdlet called Export-ImageText that can easily export text from our scanned document images.

Several popular cloud drive offerings have recently begun offering Optical Character Recognition (OCR) as a free add-on to their service. Among others, check out:

High-quality OCR was once the sole purview of tremendously expensive enterprise software. Now it's a commoditized add-on feature for cloud services. This begs the question, "How can we take advantage of modern OCR on our own systems?"

Let's start with the most accurate open-source OCR engine available: Tesseract-ocr by Google. After installing the Tesseract runtimes, one option is to automate the executable. Instead I chose to wrap up the SDK in a Windows PowerShell binary module. By doing this, we can bundle the dependencies into the module folder so that distribution is a piece of cake.

Rather than leaving this as an exercise for the reader, I've done the work and open-sourced the project here: Positronic-IO/PSImaging. To get the PSImaging module without the source, you can run this one-liner: 

& ([scriptblock]::Create((iwr -uri http://tinyurl.com/Install-GitHubHostedModule).Content))
-GitHubUserName Positronic-IO -ModuleName PSImaging -Branch 'master' -Scope CurrentUser

Now let's play...

I have a folder with sample scanned documents, including an image with the repeating text of the Quick Brown Fox. Let's start by extracting all of the text from this file: 

Image of command output

Right away I notice that running this command seemed too slow to me. In fact, it clocks in at 1.3 seconds on my machine. Luckily, we can isolate what gets read by passing in a rectangle. Let's see how limiting the scope this way affects performance.

First, we'll isolate an interesting rectangle. Here I've opened the Quick-Brown-Fox.png file in Paint, and I added a rectangle around the word "fox": 

Image showing rectangle

Paint tells us the coordinates: x,y = 172,152 h,w = 36,33. Let's add the given coordinates to System.Drawing.Rectangle: 

$rect = New-Object System.Drawing.Rectangle 172,152,36,33

Now we'll pass $rect to our Export-ImageText cmdlet:

dir .\Quick-Brown-Fox.png | Export-ImageText -Rect $rect 

Image of command output

Profiling this run of the command shows us that it took just 200 ms. That's a command I can run on a database of a million scanned images and be done in a reasonable amount of time.

Next up in this series, we'll leverage another open source technology within Windows PowerShell to automatically group images by document similarity.

~Ben

Thanks again, Ben. I'm looking forward to tomorrow's post.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Use PowerShell to Count Images

$
0
0

Summary: Learn how to use Windows PowerShell to count images.

Hey, Scripting Guy! Question How can I use Windows PowerShell to count images on a drive?

Hey, Scripting Guy! Answer Use the Get-ChildItem cmdlet, specify the path, and send the results to the Measure-Object cmdlet,
           for example:

dir -Recurse -ea 0 -File -path e:\data -include "*.gif","*.jpg", "*.png", "*.bmp" | measure

Note  Dir is an alias for Get-ChildItem and Measure is an alias for Measure-Object.

PSImaging Part 3: Group-ImageFile

$
0
0

Summary: Guest blogger, Ben Vierck, talks about grouping similar images with Windows PowerShell.

Microsoft Scripting Guy, Ed Wilson, is here. Today we have Ben Vierck back for Part 3 in his series about images. Before you begin, you might like to read:

In first two blog posts of this series, we wrote the Windows PowerShell functions Test-Image and Export-Text into a new Windows PowerShell module named PSImaging. The purpose of this exercise is to give us a set of common atomic tools that we can use to automate some of day-to-day document management tasks that require manual human intervention.

Today we're going one step further by building and using tools that give Windows PowerShell a rudimentary level of vision so that our scripts can see whether two images are similar to one another.

What do we mean by sorting images by similarity? By the end of the exercise, we want to be able to write a script that takes a folder that looks like this:

Image of folder

...and turn it into a folder that looks like this:

Image of folder

As humans, we have no problem sorting these images based on image similarity. It's a trivial task. Our computers, though, don't come with native vision. We'll have to give it these tools, one-at-a-time. Let's start with the ability to identify image similarity.

When choosing an algorithm, we want to optimize for two things:

  • Minimal compute time of runtime comparison
  • Resilience in the face of minor image transformations such as skew, resizing, and cropping

To satisfy the first goal, I've chosen to break the problem into two pieces:

  • Compute a signature which can be stored for later retrieval
  • Compare signatures

In the final production-ready module, we should store signatures on a disk for quick retrieval later. By doing so, we've moved the compute time from runtime to some other time of our choosing—for example, a nightly indexing of files. 

I've chosen a signature schema developed by H. Chi Wong, Marshall Bern, and David Goldberg of Xerox, and published in 2002: An image signature for any kind of image. The algorithm itself is brilliant, comparing relative brightness levels of regions within the image. This method means it satisfies our second requirement: resilience to some resizing, cropping, and compression.

As before, we've wrapped up an open source implementation with a Windows PowerShell layer. Our PSImaging module is stored on GitHub: Positronic-IO/PSImaging. If you haven't already, you can install the module with this one-liner: 

& ([scriptblock]::Create((iwr -uri http://tinyurl.com/Install-GitHubHostedModule).Content))
-GitHubUserName Positronic-IO -ModuleName PSImaging -Branch 'master' -Scope CurrentUser

Let's start with the Get-ImageHash cmdlet. It takes two parameters, Path and Level. Let's try it:

Get-ImageHash .\1.tiff

Image of command output

The default parameter of the new cmdlet is Path. The cmdlet returns a string that contains a hash. This is the signature described in the Wong, Bern, Goldberg paper. Now let's put it to work. Let's get hashes for two images that we know aresimilar:

$hash1 = Get-ImageHash .\1.tiff -Level 5

$hash10 = Get-ImageHash .\10.tiff -Level 5

Image of folder

Let's compare those two hashes by using the Compare-ImageHash cmdlet:

Compare-ImageHash $hash1 $hash10

Image of command output

The result is 0.8125, or 81.25%, similarity. Let's get a hash for an image that we know is not similar:

$hash2 = Get-ImageHash .\2.tiff -Level 5

Image of folder

Let's compare the two hashes of the images we know are not similar:

Compare-ImageHash $hash1 $hash2

Image of command output

The result is 0.5241699, or 52.42%, similarity. This confirms what we can see visually. Images 1 and 10 are significantly more similar than images 1 and 2.

That's useful on a case-by-case basis. Let's put it to work on a whole collection of images by using the Group-ImageFile cmdlet. Under the covers, the Group-ImageFile cmdlet uses Get-ImageHash and Compare-ImageHash to sort a collection of files into groups. Let's see how it works:

$groups = dir | Group-ImageFile

$groups

Image of command output

Now let's examine the files that were grouped with a High degree of confidence: 

$groups | ? Confidence -eq High | select -ExpandProperty Files

Image of command output

Let's check the output visually:

Image of folder

That's perfect.

Now you're armed with the right tools to start managing scanned document images. As we've shown in this short series, Windows PowerShell has a limitless capacity to be extended with very little effort. The project on display here was written in under an hour. Imagine how powerful it would be if we'd put in 150 hours.

With legacy imaging systems, most of the effort goes into getting the images processed and put into the system. It's a fragile process. Imagine instead, that you could send your processes to the images where they live. The characteristic of such a system would follow the philosophy of Windows PowerShell: repeatable, transparent, and completely scriptable.

Follow me at @xcud on Twitter to keep abreast of the latest in Windows PowerShell, document imaging, and computer vision.

~Ben

Thank-you, Ben, for an insightful series.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 


PowerTip: Use PowerShell to Count Types of Images

$
0
0

Summary: Use Windows PowerShell to count different types of image files.

Hey, Scripting Guy! Question How can I use Windows PowerShell to see a count of the different types of image files on my computer?

Hey, Scripting Guy! Answer Use the Get-ChildItem cmdlet to find the image files and then pipe the results to the Group-Object cmdlet,
           for example:

dir -Recurse -ea 0 -File -path e:\data -include "*.gif","*.jpg", "*.png", "*.bmp" | 
group extension -NoElement

Note  Dir is an alias for Get-ChildItem and Group is an alias for Group-Object.

Weekend Scripter: Use PowerShell to Find Longest Cmdlet Name

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to find the longest cmdlet name.

Microsoft Scripting Guy, Ed Wilson, is here. This is an exciting time of the year, as we get ready for the PowerShell Summit in Charlotte and for the Microsoft Ignite conference in Chicago. I have been having numerous email conversations with various individuals the past week. One of the fun exchanges took place with Windows PowerShell MVP and Honorary Scripting Guy, Sean Kearney. We are planning something cool for Ignite, and we were bouncing ideas around, for example, "I wonder which Windows PowerShell cmdlet name is the longest?" I know there are some long ones, especially when it comes to the NetAdapter or TCP cmdlets, but which is longest? And of course, how long is it really?

I played around for a couple of minutes and came up with a Windows PowerShell script that provides the answer. Let’s take a look…

First I need to get the cmdlet names (and also the CIM-wrapped functions). So I use the Get-Command cmdlet to retrieve all of the cmdlets. I then pipe the CmdletInfo object to Select-Object where I create a custom object that provides the name and the length of each name. Let's take a look at the first part of that command:

Get-Command |

Select-Object @{label = 'name'; expression = {$_.name}}, 

I use Select-Object to create the custom object. The first property of that object is the name. I pick that up directly from the Name property of the CmdletInfo object that streams across the pipeline. To get the length of the name, I need to use the Measure-Object cmdlet.

The name of the second property of my custom object is Length. I take the name from the CmdletInfo object and pipe it to Measure-Object and tell it to count characters. I then grab the Character property. This portion of the command is shown here:

@{label = 'length'; expression = {($_.name |

  Measure-Object -Character).Characters}} |

Now I need to sort the custom object by the Length property. I tell it to sort in descending order:

Sort-Object length -Descending | 

Lastly, I use the Select-Object cmdlet again to return the first item from the sorted list:

Select-Object -First 1

The complete script is shown here:

Get-Command |

Select-Object @{label = 'name'; expression = {$_.name}},

@{label = 'length'; expression = {($_.name |

  Measure-Object -Character).Characters}} |

Sort-Object length -Descending |

Select-Object -First 1

As shown here, when I run the script, it tells me the name of the longest cmdlet:

Image of command output

Dude, 47 characters long! I am sure glad for Tab completion. There is one problem with the output, and that is that the cmdlet name is sooooooooooooooooooooooooooo long that it exceeds the screen size. If I reduce the size of the output, the cmdlet name fits. This is shown here:

Image of command output

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Use PowerShell to Verify Hotfix

$
0
0

Summary: Use Windows PowerShell to verify if a hotfix is installed.

Hey, Scripting Guy! Question How can I use Windows PowerShell to verify that my workstation running Windows 8.1 has
           the November DSC rollup installed?

Hey, Scripting Guy! Answer Use the Get-Hotfix cmdlet and search for ID KB3000850:

Get-HotFix -Id kb3000850

Use PowerShell to Enable Wi-Fi

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to enable Wi-Fi if Ethernet is not connected.

Hey, Scripting Guy! Question Hey, Scripting Guy! Most of the time when I use my laptop running Windows 8.1, I am plugged in to the Ethernet network at work. Occasionally, I unplug the laptop and take it to meetings. I keep the Wi-Fi disabled when I am plugged in to the Ethernet because if I have it enabled, it seems that at random times, my laptop connects to Wi-Fi.

I only notice this when Outlook becomes really slow or I am trying to download something that ends up taking forever. I also think this is a security issue, because I should know what network I am connected to. But it seems to take me forever to make the change between enabling and disabling various network adapters. Can I script this?

—SC

Hey, Scripting Guy! Answer Hello SC,

Microsoft Scripting Guy, Ed Wilson, is here. This past weekend was pretty cool. The weather was wonderful and I was caught up on my book project, so the Scripting Wife and I headed out for a weekend excursion. Because we really did not have a set agenda, and because we had no specific timetable to adhere to, we sort of winged it. Armed with a couple of Windows Phones, a couple of Surfaces, and a couple of Zunes, we were ready for anything.

We ended up in a small town outside of Charlotte, and I found a highly recommended tea room on Yelp. So we detoured to go there. The tea selection was OK, but not great, so I opted for a triple shot espresso instead. Teresa, of course, had hot chocolate. Anyway, I like hand-crafted coffee if I don’t have to make it, and if there is not any good tea around. But like most things, if it becomes a regular thing, I want to automate it.

Your question gives me a perfect start for Hardware Week. One of the issues with my laptop is that it seems to keep growing new network adapters. Every time I update drivers or software, it seems that a new network adapter pops up. I can navigate to the network adapter folder. It is shown here:

Image of folders

But I can also use the Get-NetAdapter cmdlet to display the network adapters and their status. This is shown here:

Image of command output

I can use Get-NetAdapter and a wildcard configuration to find network adapters, or I can use the Name or InterfaceDescription properties. Each computer will have its own collection of adapters and names for those devices. Therefore, each script will need to be matched to the workstation that will run the script. It is possible to simplify this process by naming each adapter the same—but as I said, over time, my laptop seems to grow new network adapters, and these tend to take on default names.

For me, the best approach was to disable everything if I am not connected via the Ethernet cable, and then turn on the adapters I need. Because the command to disable the network adapters tends to be slow (or at least, take varying amounts of time), I decided to call the command as a job and wait for the job to complete. Because the cmdlet prompts by default, I set –Confirm to $False. This command is shown here:

If ( (Get-NetAdapter -Name 'Ethernet').Status -ne 'Connected' )

    {

      Get-NetAdapter | Disable-NetAdapter -Confirm:$false -AsJob | Wait-Job 

Now I need to enable the network adapters I want to use. Here is the command I use:

Enable-NetAdapter -Name 'Wi-Fi'-Confirm:$false

Enable-NetAdapter -Name 'Ethernet 3' -Confirm:$false

The complete script is shonw here:

If ( (Get-NetAdapter -Name 'Ethernet').Status -ne 'Connected' )

    {

      Get-NetAdapter | Disable-NetAdapter -Confirm:$false -AsJob | Wait-Job

      Enable-NetAdapter -Name 'Wi-Fi'-Confirm:$false

      Enable-NetAdapter -Name 'Ethernet 3' -Confirm:$false

      }

    Note  You need to run this script with admin rights. It also uses Windows 8.1, and therefore, it will not work on Windows 7.
    I talk about other approaches for performing this task in the Hey, Scripting Guy! Blog post called Enabling and Disabling Network Adapters.

SC, that is all there is to using Windows PowerShell to enable Wi-Fi if Ethernet is not connected. Hardware Week will continue tomorrow when I will talk about disabling Wi-Fi if Ethernet is connected.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

PowerTip: Use PowerShell to Disable All Network Adapters

$
0
0

Summary: Use Windows PowerShell to disable all network adapters on your system.

Hey, Scripting Guy! Question I often work in offline situations, and I would like to disable all network adapters on my laptop for security reasons
           and to help to extend battery life. How can I use Windows PowerShell to do this easily?

Hey, Scripting Guy! Answer Use the Get-NetAdapter cmdlet to retrieve all network adapters and pipe the results to the 
           Disable-NetAdapter cmdlet. To suppress confirmation messages use the –Confirm parameter
           and set it to $false:

Get-NetAdapter | Disable-NetAdapter -Confirm:$false

Note  This command requires admin rights.

Use PowerShell to Disable Wi-Fi

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to disable Wi-Fi if Ethernet is connected.

Hey, Scripting Guy! Question Hey, Scripting Guy! When I have an Ethernet cable plugged in to my laptop and I am connected to a network, I want to ensure that everything else is disabled. How can I do that by using Windows PowerShell?

—DH

Hey, Scripting Guy! Answer Hello DH,

Microsoft Scripting Guy, Ed Wilson, is here. This morning is a bit cool, and definitely moist outside. It is not raining, but it seems like it could easily do so. It sort of reminds me of winter mornings in Florida, when it was cool and damp. I have not seen any seagulls, but I would not be surprised if a few appeared. The big thing is to be prepared, because the weather can change suddenly and dramatically.

DH, the thing I like to do when setting up my laptop, or when setting up servers, is to provide meaningful names for things. If I rely on Windows naming conventions, I end up not aware of what a lot of stuff actually is. But by naming them myself, I can confirm that I know what each device really does.

This is the way I found out what was going on with my laptop after I added the Hyper-V role because it changed, added, and modified the way the network adapters did things. So, the best way to know what is going on is to make sure that you name network adapters so you know what they are. Here is a screenshot of my network adapters:

Image of folder

I have named my Ethernet, my virtual Ethernet adapters, the Wi-Fi, and the virtual Wi-Fi. I also renamed my internal switch. I did not bother naming the other stuff. This enables me to easily separate the network adapters I am concerned with.

To write the script, the first thing I need to do is to find if the Ethernet wired network connection is actually connected. If it is connected, I want everything except my virtual Ethernet adapter disabled. Here is the code that I use to determine if I am connected via Ethernet:

If ( (Get-NetAdapter -Name 'Ethernet').MediaConnectionState -eq 'Connected' )

Now I need to find everything that is not “Ethernet.” This is actually easy to do because I name my network adapters “Ethernet” or something like that. So in this line of code, I simply look for all network adapters not named something like Ethernet:

  (Get-NetAdapter).where({$psitem.name -notmatch 'ethernet'}) |

I now pipeline the resulting network adapters to the Disable-NetAdapter cmdlet, and I specify that I do not want confirmation:

  Disable-NetAdapter -Confirm:$false

When I run the script, I can see the results in the Network Connections pane. I check there because there is no output from the script as it is written. Here are the resulting changes:

Image of folder

For those who were paying close attention to the images, there was an “Ethernet 3” adapter that appeared in the first image, but it is not present in the second. This is where naming stuff makes it easier to keep track of things. The “Ethernet 3” adapter seems to appear when wireless is enabled, and it disappears when it is disabled.

This is why I do not need to worry about it in my script. Based on yesterday's script (see Use PowerShell to Enable Wi-Fi), I can say when Ethernet 3 is not enabled, wireless on my laptop does not appear to work. Based on that, I know it is important, but I really do not know where it came from, where it goes, or why Windows seems to feel it needs it. Oh well, the script works, and it does what I want it to do.

DH, that is all there is to using Windows PowerShell to disable network adapters. Join me tomorrow when Hardware Week continues, and I will talk about more cool stuff. Now, I think I will go outside and look for seagulls.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Use PowerShell to See Who Can Access Folder

$
0
0

Summary: Use Windows PowerShell to see who has access to a folder.

Hey, Scripting Guy! Question How can I use Windows PowerShell to see who has access rights to a folder?

Hey, Scripting Guy! Answer Use the Get-Acl cmdlet, specify the folder, and look at the access property, for example:

(Get-Acl c:\fso).Access

 


Use PowerShell to Explore Old Windows VHDs

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to explore old Windows VHDs.

Hey, Scripting Guy! Question Hey, Scripting Guy! It is time for spring clean-up around our datacenter. I am not talking about chasing out dust bunnies, although that is part of it. What I am talking about is trying to determine what can be safely deleted without annoying the users too badly.

We have a lot of virtual machines that could have data on them, but I do not know the passwords, and I do not want to go to the trouble of creating virtual machines, mounting the drives, and launching the things to see if there is any user data on the virtual drive. How can I do this quickly and easily—and safely? To be honest I am a bit concerned about the safety of launching a virtual machine that may not have been updated in over a year. Any tips would be appreciated.

—JB

Hey, Scripting Guy! Answer Hello JB,

Microsoft Scripting Guy, Ed Wilson, is here. Today it is cold and rainy, but I guess that is a good thing. In fact, we even turned the heat on for a little while. I am sipping a nice cup of cinnamon tea that I made: a spoon of English Breakfast tea, two cinnamon sticks, and a splash of nutmeg for fun. It is great with a little bit of milk in it—kind of like drinking a cup of cinnamon toast, only not.

Anyway, JB, you need to approach your virtual hard disk (VHD) issue as a disk or folder issue, and not as a Windows issue. So it is like mounting a VHD, only not.

The trick is to mount the Windows image into a folder so that I can use normal Windows PowerShell cmdlets to peruse it for any data that might need to be archived. When I have the data archived, I can delete the VHD and reclaim my lost disk space. My laptop running Windows 8.1 has the necessary cmdlets. In fact, for my purposes, I need the following Windows PowerShell commands:

  • Get-ChildItem to find the Windows images
  • MD to create a folder that I will use to mount the Windows image
  • Mount-WindowsImage to actually mount the Windows image to the newly created folder
  • Dismount-WindowsImage to dismount the Windows image after I am finished looking at it
  • RD to delete the folder that I used to mount the Windows image

Here is how it all works...

The first thing I do is use the Get-ChildItem cmdlet to search for virtual hard disks that contain the name 2003. In my naming scheme, this will work because I tend to use the version of the operating system as part of the virtual hard disk name. I also use a wildcard character because I cannot remember if the virtual hard disks are VHD or VHDX.

It seems that at some time, Hyper-V changed the file extension, but to be honest, I do not remember when that change actually took place, so I use a wildcard character to ensure that I get the required files. In addition, even if I did actually create a VHD, it is possible that at some date, I may have upgraded the version to a VHDX, so it is always better to be safe than to be sorry. Here is my command:

$image = Get-ChildItem -Path e: -Filter *2003*.vhd* -Recurse -File

Now I create a folder that I will use to mount the Windows image. To do this, I use MD because it is quick. I store the returned directory info object so I can pick up my path. This command is shown here:

$path = md c:\image

I use the Mount-WindowImage cmdlet to mount the Windows image in the virtual hard disk to the C:\image folder. There are three required parameters:

  • ImagePath: The complete path to the virtual hard disk that contains the version of Windows to mount. It points to an actual file, not to a folder.
  • Path: The path to the folder that will hold the mounted Windows image.
  • Index: For a virtual hard disk type of file, it is always an index of 1. This can be different if mounting a WIM, but that is not what I am doing here, so Index is 1.

Here is the command:

Mount-WindowsImage -ImagePath $image.FullName -Path $path.FullName -Index 1

When I run the command, I see a progress bar appear, and then the output tells me the path to find my mounted image, if it is online, and if it needs to be restarted. The progress bar is shown here:

Image of command output

I can navigate to the C:\Image folder. It looks exactly like it would if I was to load Windows and navigate around. As shown here, I even see the page file:

Image of menu

If I click something, it might tell me that I do not have permission to the folder. I can click Continue to obtain permission, and then see what is there. Here is the message:

Image of command output

I can easily copy anything I need to from the VHD. When I am done, I dismount the installation of Windows, and remove my folder. Because I am not making any changes, I do not need to worry about saving changes or anything like that. Here is the script I wrote to dismount Windows:

$path = "C:\image"

Set-Location c:\

Dismount-WindowsImage -Path $path -Discard

RD $path -Force

This works a lot better than having to create a bunch of virtual machines for potentially obsolete versions of Windows that one finds laying around on miscellaneous virtual hard disks.

JB, that is all there is to using Windows PowerShell to explore old Windows VHDs. Join me tomorrow for more Hardware Week when I will talk about more cool Windows PowerShell stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Use PowerShell to Find Virtual Hard Disks

$
0
0

Summary: Use Windows PowerShell to find virtual hard disks on your system.

Hey, Scripting Guy! Question I notice that my disk space appears to be disappearing at a rapid rate, and I suspect someone is creating
           virtual hard disks (VHDs) and forgetting about them. How can I use Windows PowerShell to find all the
           VHDs on my system?

Hey, Scripting Guy! Answer Use the Get-ChildItem (LSDirgci are aliases), use a filter like *.vhd*, and specify the Recurse switched
           parameter. To simply report on the path to the VHDs, select the FullName parameter. Here is an example:

Get-ChildItem -Path e: -Filter *.vhd* -Recurse -File | select fullname

Use PowerShell to Add Files to Offline Windows Image

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to add files to an offline Windows image.

Hey, Scripting Guy! Question Hey, Scripting Guy! I have a number of VHDs that I need to add files to. I know that I can use File Manager to mount the VHD and then copy the files, but I am hoping that I can use Windows PowerShell to do this. The VHDs are for virtual machines that I run in my lab. Can you help?

—AM

Hey, Scripting Guy! Answer Hello AM,

Microsoft Scripting Guy, Ed Wilson, is here. Today I am a bit sore. I finally made it back into the gym yesterday, and my trainer seemed to have it in for me. Anyway, I am listening to Alan Parsons on my Zune, sipping a nice cup of Earl Grey tea, and going through my email to scripter@microsoft.com. I am thinking that later tonight I might make it back to the gym to work out the soreness.

Speaking of sore...

AM, just reading about all that mousing around makes my wrist hurt. The good thing is that Windows PowerShell can indeed mount and service offline Windows images. (This functionality was introduced in Windows 8 and Windows Server 2012.)

One of the things I like to do is to put things back where I found them—this includes my current working directory. So, I love to use Push and Pop. Push-Location stores the current working location. I use it before I set my location using Set-Location. I do not need to use a variable and Get-Location because Windows PowerShell already knows about a “location stack.” All I need to do is call Push-Location, and Windows PowerShell will automatically remember where I start from. Here is the code:

Push-Location

Set-Location c:\

Because I want things to occur in a specific order, I create a simple workflow—and I do mean simple. It is no more complicated than a very basic Windows PowerShell function. I use the Workflow keyword, specify a name, and create some variables. This is shown here:

WorkFlow Service-Image

{

 $image = "E:\vms\vhd\c1\c1.vhdx"

 $path = "c:\image"

 $scripts = 'C:\PoshScripts'

Now I create the folder that will hold my mounted image, and I use the Sequence keyword to specify that I want commands to appear in a certain order:

MD $path

  Sequence {

I want to first mount my Windows image, then I want to copy some Windows PowerShell scripts to a folder, then I want to dismount my Windows image and remove the folder that I created. All this is shown here:

Mount-WindowsImage -ImagePath $image -Path $path -Index 1

   Copy-Item -Path $scripts -Destination "C:\image\poshScripts" -Recurse

   Dismount-WindowsImage -Path $path -Save

   RD $path -Force

 } }

The last things I do is call my workflow and pop back to my original working directory. This is shown here:

Service-Image

Pop-Location

Here is the complete script:

Push-Location

Set-Location c:\

WorkFlow Service-Image

{

 $image = "E:\vms\vhd\c1\c1.vhdx"

 $path = "c:\image"

 $scripts = 'C:\PoshScripts'

 MD $path

  Sequence {

   Mount-WindowsImage -ImagePath $image -Path $path -Index 1

   Copy-Item -Path $scripts -Destination "C:\image\poshScripts" -Recurse

   Dismount-WindowsImage -Path $path -Save

   RD $path -Force

 } }

Service-Image

Pop-Location

As shown in the following image, when I run the script, I see a few things in the output:

Image of command output

One thing I see is a path to the DISM log. So I go there and open the log in Notepad. The log is shown here:

Image of files

One thing to note is that the DISM log appends to the end. So I need to go all the way to the end of the log to see what happened during my Windows PowerShell script. I see that it said everything was successful. Cool. I decide to mount the image via the Windows Files Explorer so that I can check to ensure that my Windows PowerShell scripts were indeed copied. As shown in the following image, it was a success:

Image of menu

AM, that is all there is to using Windows PowerShell to add files to a Windows image. Join me tomorrow when I will talk about more cool Windows PowerShell stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

PowerTip: Find DISM-Related PowerShell Cmdlets

$
0
0

Summary: Learn how to find all Windows PowerShell cmdlets that work with DISM.

Hey, Scripting Guy! Question How can I find all Windows PowerShell cmdlets that work with Deployment Image Servicing and Management (DISM)?

Hey, Scripting Guy! Answer Use the Get-Command cmdlet and specify the DISM module:

Get-Command -Module dism

Use PowerShell to Remove Optional Features from Windows VHD

$
0
0

Summary: Microsoft Scripting Guy, Ed Wilson, talks about using Windows PowerShell to remove optional features from Windows virtual hard disks.

Hey, Scripting Guy! Question Hey, Scripting Guy! I have a number of virtual machines that have optional features that they do not need, such as Windows Media Player. In fact, the virtual machines are set up with no audio, so having Windows Media Player is sort of a waste. How can I easily remove this feature?

—CG

Hey, Scripting Guy! Answer Hello CG,

Microsoft Scripting Guy, Ed Wilson, is here. This morning started off early. IIS MVP, Teri Donahue, stopped by to pick up Teresa’s Surface Pro 2. Teri and Teresa are meeting at an MVP conference in Philly. I had to get a new SIM card for the Surface because the dial-up adapter quit working, and Teri is taking it to Teresa, who is pretty much going through withdraw without her device.

Not that she is completely deviceless, she had my Surface Pro 2 and her original Surface RT with her, not to mention her Windows Phone, but still she was jonesing without her favorite device. Thanks Teri! Anyway, I took the early morning wake up to head to the gym to get my workout over so I could get back to work answering email sent to scripter@microsoft.com.

CG, speaking of withdrawl…

It is actually pretty easy to use Windows PowerShell to remove unneeded features from your VHDs. (This technique also works for WIM images.)

One of the cool things about using the DISM cmdlets is that it permits me to work with optional features in a supportable and reliable method. In the old days, I had to worry about dependencies and hope that I did not break things when I used tools that were designed to shrink Windows images, but that is no longer the case.

The first thing I do is mount an image that I want to work with to a folder. Here is the code I use:

$image = "E:\vms\vhd\c1\c1.vhdx"

$path = "c:\image"

 MD $path

 Mount-WindowsImage -ImagePath $image -Path $path -Index 1

After I have my virtual machine mounted to a folder, I use the Get-WindowsOptionalFeature to look at the optional features:

Get-WindowsOptionalFeature -Path $path

The following image shows the output from this command:

Image of command output

To find related features, use wildcard characters. The following command illustrates finding media-related features:

PS C:\> Get-WindowsOptionalFeature -Path C:\image -FeatureName *media*

Feature Name      : MediaPlayback

Display Name      : Media Features

Description       : Controls media features such as Windows Media Player and Windows Media Center.

Restart Required  : Possible

State             : Enabled

Custom Properties :

                    \SoftBlockLink : http://go.microsoft.com/fwlink?LinkID=153156        

Feature Name      : WindowsMediaPlayer

Display Name      : Windows Media Player

Description       : Windows Media Player

Restart Required  : Possible

State             : Enabled

Custom Properties :

                    \SoftBlockLink : http://go.microsoft.com/fwlink?LinkID=140092

To remove an optional Windows feature, I pipe the results of the Get-WindowsOptionalFeature cmdlet to the Disable-WindowsOptionalFeature cmdlet. I also use the –Remove switch to actually remove the feature and not simply disable the feature. This command is shown here (this is a single-line command that wraps at the pipe character for readability):

Get-WindowsOptionalFeature -Path C:\image -FeatureName *media* |

Disable-WindowsOptionalFeature -Remove

Now, I put everything together into a script:

Push-Location

Set-Location c:\

WorkFlow Service-Image

{

 $image = "E:\vms\vhd\c1\c1.vhdx"

 $path = "c:\image"

 MD $path

  Sequence {

   Mount-WindowsImage -ImagePath $image -Path $path -Index 1

   Get-WindowsOptionalFeature -Path C:\image -FeatureName *media* |

   Disable-WindowsOptionalFeature -Remove

   Dismount-WindowsImage -Path $path -Save

   RD $path -Force

 } }

Service-Image

Pop-Location

When I run the script, the following output appears:

Image of command output

CG, that is all there is to using Windows PowerShell to remove optional features from virtual machines. Join me tomorrow when I will have a guest blog post by Windows PowerShell MVP, Teresa Wilson. She will talk about plans for the Scripting Guys Ignite booth.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Viewing all 2129 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>