This is something which came out from the Files Deployment Using Powershell series which I wrote. I have created the program as a ps1 file so that i can integrate it to my powershell sessions, if not I will need to copy the entire function (40+ lines of code) into my powershell session before i can start using the program. But all thanks to default security settings that are in place which prevent execution of scripts in powershell, you might get the below error.

Simplest way to overcome this is to get the rights to execute the ps1 scripts in powershell. But hey, I am about doing things differently so I went about figuring another way to get things done.

Introducing my script to others makes me realize most of them are uncomfortable with the thing called powershell preferring the good old command prompt. Doing some research over the web for a solution brought me to a post from Dmitry Sotnikov who wrote about running powershell script in a batch file.

So running my program in a single command(by virtue of a batch file) – checked, getting others to run my powershell program while taking into consideration their unfamiliarity with powershell(we will be running powershell script in command prompt via a batch file) – checked. There are actually two ways of getting this done.

The first way is to encode the powershell commands which I am not going to talk about it as it is quite straightforward and Dmitry’s post should more or less covered it.

The second way is to “flatten” all the commands into a single line of powershell code which in itself is challenging as command prompt does not work well with some codes written in powershell so it will involve substituting quotes(‘) and double quotes(“) and backslashes() (because of the way command prompt interprets double quotes).

I will be using the code of Files Deployment Using Powershell for example. The techniques could be reused on other powershell programs you wrote. So here we go using the 2nd method.

No comments required for this

If you wrote comments(those with a hashtag # in front) on your powershell program, it is time now to strip it. Having comments on it might mean misinterpretation by the command prompt, what more they are not necessary to execute the program. The key here is to keep the minimum required code in order for the program to run in command prompt, having more just means more chance of issues.

======BEFORE====================================

function Appimpl
{
$filename = @() #array for filename
$fullfilename= @() #array for the fullfilename including the path
$objects=@() #array for the objects

======AFTER====================================

function Appimpl
{
$filename = @()
$fullfilename= @()
$objects=@()

Not much space left

The task is to keep the minimum amount of code. So we do not need those fancy space to keep the code looking tidy. At the end, everything will be squeezed together. So remove any unnecessary space and trailing space at the end of line.

======BEFORE====================================
function Appimpl
{
     $filename = @()
     $fullfilename= @()
     $objects=@()
======AFTER====================================
function Appimpl
{
$filename = @()
$fullfilename= @()
$objects=@()

Squeeze Codes! Squeeze!

Try to squeeze as much code as a one liner as possible. This will keep the code more compact. Code like the curly bracket can usually be squeezed to the preceding line

======BEFORE====================================
function Appimpl
{
$filename = @()
$fullfilename= @()
$objects=@()
======AFTER====================================
function Appimpl{
$filename=@()
$fullfilename=@()
$objects=@()

Lost in Translation

Command prompt has issues interpreting double quotes. So in order to translate double quotes to be understandable by command prompt, there are 3 types of translation required

1st Type: Convert all double quotes for enclosing actual text to single quote ======BEFORE====================================
inputsource = Read-host Input the source path
$inputDestination = Read-host Input the destination path
======AFTER====================================
inputsource = Read-host Input the source path
$inputDestination = Read-host Input the destination path

2nd Type: Convert all double quotes for enclosing variables to backslash double quote

======BEFORE====================================
$itemsize = (Get-Item $fullfilepath\$item).length
$itemdate = (Get-Item $fullfilepath\$item).LastWriteTime
$stream = ([IO.StreamReader]$fullfilepath\$item).BaseStream

======AFTER====================================
$itemsize = (Get-Item \”$fullfilepath\$item\”).length
$itemdate = (Get-Item \”$fullfilepath\$item\”).LastWriteTime
$stream = ([IO.StreamReader]\”$fullfilepath\$item\”).BaseStream

3rd Type: Convert all single backslash to double backslash except for those backslash created by 2nd Type

======BEFORE====================================
$itemsize = (Get-Item \“$fullfilepath\$item\”).length
$itemdate = (Get-Item \“$fullfilepath\$item\”).LastWriteTime
$stream = ([IO.StreamReader]\“$fullfilepath\$item\”).BaseStream
======AFTER====================================
$itemsize = (Get-Item \“$fullfilepath\\$item\”).length
$itemdate = (Get-Item \“$fullfilepath\\$item\”).LastWriteTime
$stream = ([IO.StreamReader]\“$fullfilepath\\$item\”).BaseStream

End it with a semicolon

At the end of each line, add a semicolon. This is to interpret a different line by command prompt.

======BEFORE====================================
$itemsize = (Get-Item \“$fullfilepath\\$item\”).length
$itemdate = (Get-Item \“$fullfilepath\\$item\”).LastWriteTime
$stream = ([IO.StreamReader]\“$fullfilepath\\$item\”).BaseStream
======AFTER====================================
$itemsize = (Get-Item \“$fullfilepath\\$item\”).length;
$itemdate = (Get-Item \“$fullfilepath\\$item\”).LastWriteTime;
$stream = ([IO.StreamReader]\“$fullfilepath\\$item\”).BaseStream;

Squeeze everything into 1 liner by removing the Carriage Return/Line Feed

Squeeze everything into one line which is to form a single line with all the code you have left.

Add some prefix and suffix to make it work

Add the text powershell.exe -command “ to the front and ;appimpl” to the end of the code. This will be all that is required to make it work. You can save the code as a .bat file and run it from command prompt and you will have a powershell program running from the command prompt. My ending code will look like the below:

powershell.exe -command “function Appimpl {;$filename = @();$fullfilename= @();$objects=@();do {;$input = Read-host ‘Input the filename [Hit Enter for last value]’;if ([string]::IsNullOrEmpty($input)){}else{$filename += $input};} until ([string]::IsNullOrEmpty($input));$inputsource = Read-host ‘Input the source path ‘;$inputDestination = Read-host ‘Input the destination path ‘;’Start of Deployment: ‘ +(get-date -format F).ToString() + [string][TimeZoneInfo]::Local.DisplayName;ForEach ($item in $filename) {;Copy-Item -Path $inputsource\\$item -force -Destination $inputDestination;$SourceOrDestinations = @((‘Source’, $inputsource),(‘Destination’,$inputDestination));ForEach($choice in $SourceOrDestinations){;$object = New-Object PSObject -Property @{Path = $item};$fullfilepath=$choice[1];$itemsize = (Get-Item \\"$fullfilepath\\\\$item\\").length;$itemdate = (Get-Item \\"$fullfilepath\\\\$item\\").LastWriteTime;$stream = ([IO.StreamReader]\\"$fullfilepath\\\\$item\\").BaseStream;[string]$hash = -join ([Security.Cryptography.HashAlgorithm]::Create( ‘MD5’ ).ComputeHash( $stream ) | ForEach {{0:x2}-f $_ });$stream.Close();$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Location' -Value $choice[0] -PassThru;$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Size' -Value $itemsize -PassThru;$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Date Modified' -Value $itemDate -PassThru;$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Hash' -Value $Hash -PassThru;$objects += $object}};Write-Host 'Source Path:'' $inputsource;Write-Host 'Destination Path:' $inputDestination;write-output $objects | format-table -autosize;'End of Deployment: '' +(get-date -format F).ToString() + [string][TimeZoneInfo]::Local.DisplayName};appimpl”

Some more enhancement

The ending code above will actually be enough for you to run the program. But running it will show the below screenshot(which is very untidy)

To make the program more presentable, add a @echo off as the 1st line of the batch file and now the program will look more presentable as shown below

So below will be my final code saved in batch file:

@echo off
powershell.exe -command “function Appimpl {;$filename = @();$fullfilename= @();$objects=@();do {;$input = Read-host ‘Input the filename [Hit Enter for last value]’;if ([string]::IsNullOrEmpty($input)){}else{$filename += $input};} until ([string]::IsNullOrEmpty($input));$inputsource = Read-host ‘Input the source path ‘;$inputDestination = Read-host ‘Input the destination path ‘;’Start of Deployment: ‘ +(get-date -format F).ToString() + [string][TimeZoneInfo]::Local.DisplayName;ForEach ($item in $filename) {;Copy-Item -Path $inputsource\\$item -force -Destination $inputDestination;$SourceOrDestinations = @((‘Source’, $inputsource),(‘Destination’,$inputDestination));ForEach($choice in $SourceOrDestinations){;$object = New-Object PSObject -Property @{Path = $item};$fullfilepath=$choice[1];$itemsize = (Get-Item \\"$fullfilepath\\\\$item\\").length;$itemdate = (Get-Item \\"$fullfilepath\\\\$item\\").LastWriteTime;$stream = ([IO.StreamReader]\\"$fullfilepath\\\\$item\\").BaseStream;[string]$hash = -join ([Security.Cryptography.HashAlgorithm]::Create( ‘MD5’ ).ComputeHash( $stream ) | ForEach {{0:x2}-f $_ });$stream.Close();$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Location' -Value $choice[0] -PassThru;$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Size' -Value $itemsize -PassThru;$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Date Modified' -Value $itemDate -PassThru;$object = Add-Member -InputObject $Object -MemberType NoteProperty -Name 'Hash' -Value $Hash -PassThru;$objects += $object}};Write-Host 'Source Path:'' $inputsource;Write-Host 'Destination Path:' $inputDestination;write-output $objects | format-table -autosize;'End of Deployment: '' +(get-date -format F).ToString() + [string][TimeZoneInfo]::Local.DisplayName};appimpl”

Do User Acceptance Test