how to Execute a cmd file in my ps script? - deployment

I have a cmd file which calls an msi and passes paramters. I am calling this deploy.cmd file from a powershell script. How can i achive this?
I am may be missing something here.
This is what my cmd looks like,
Msiexec /i ABCInstaller.msi ^
DB.SERVER=ABC\QA ^
APPLICATION.ENV.TYPE=Qa ^
SVCIDENTITY=SVC-QA#ABC.com ^
SVCPASSWORD=xxx ^
LOCAL.EMAILING="true" ^
EMAIL.GMAT="tarun.arora#abc.com" ^
EMAIL.GMATR="tarun.arora#abc.com" ^
EMAIL.SUCCESSFUL.VALIDATION.SUBJECT="[QA] Successful validation of ABC Message" ^
/lv "ABC_Installer_QA_Log.txt"
This is what my powershell script looks like,
# Assigning Build Number and Drop Location for the MSI in scope
$buildNumber = $TfsDeployerBuildData.BuildNumber
$dropLocation = $TfsDeployerBuildData.DropLocation
# Assign values
if($buildNumber -eq $null)
{
$buildNumber = $args[0]
$dropLocation = $args[1]
}
# Move old uninstall folder to Archive folder
Move-Item "D:\deploy\ABC_Uninstalled\*" "D:\deploy\ABC_Archive" -force
# Move old build folder to uninstalled folder
Move-Item "D:\deploy\ABC_Installed\*" "D:\deploy\ABC_Uninstalled" -force
# Logging
Add-Content -Path "C:\Log\TfsDeployer_Log.txt" -Value $dropLocation
Add-Content -Path "C:\Log\TfsDeployer_Log.txt" -Value $buildNumber
# Copy the msi from drop location to local physical drive
Copy-Item $dropLocation "D:\deploy\ABC_Installed" -recurse
Add-Content -Path "C:\Log\TfsDeployer_Log.txt" -Value "Copied the Msi to D:\deploy\Installed"
# Start execution
& "D:\deploy\ABC_Installed\$buildNumber\en-us\ETRM_QA.cmd"
However when the ps is executed, It prints out what is inside the cmd file rather than executing it, so the output of the execution is
Output: C:\WINDOWS\system32>Msiexec /i ABCInstaller.msi ^
DB.SERVER=ABC\QA ^
APPLICATION.ENV.TYPE=Qa ^
SVCIDENTITY=SVC-QA#ABC.com ^
SVCPASSWORD=xxx ^
LOCAL.EMAILING="true" ^
EMAIL.GMAT="tarun.arora#abc.com" ^
EMAIL.GMATR="tarun.arora#abc.com" ^
EMAIL.SUCCESSFUL.VALIDATION.SUBJECT="[QA] Successful validation of ABC Message" ^
/lv "ABC_Installer_QA_Log.txt" /passive T
The cmd file is not executed :-(

Try:
Invoke-Expression "D:\deploy\ABC_Installed\$buildNumber\en-us\ETRM_QA.cmd"

MsiExec likely is executing, you are just not seeing it because it launches as a background process that immediately returns control to cmd. For example, if I create a cmd script that looks like this:
"C:\Program Files\Microsoft Office\Office11\WINWORD.EXE"
And invoke it like this:
&launchWord.cmd
All I see on the powershell console is the contents of the cmd script, but word opens in another window.
Are you sure msiexec isn't just launching and failing, rather than failing to launch?

Related

Why do some of my PowerShell scripts stop working when I set PowerShell as the default application?

Here are 2 of my scripts. The first shows a list of directories with more than one file. The second is just a test because I never used $PSScriptRoot before.
Get-ChildItem -Path $PSScriptRoot -Directory -Name -Recurse |
Write-Output
Where-Object { (Get-ChildItem $_ -File).Count -ge 2 }|
ForEach-Object { ".\$_"} |
Invoke-Item
# If running in the console, wait for input before closing.
if ($Host.Name -eq "ConsoleHost")
{
Write-Host "Press any key to continue..."
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
}
Write-Output $PSScriptRoot
# If running in the console, wait for input before closing.
if ($Host.Name -eq "ConsoleHost")
{
Write-Host "Press any key to continue..."
$Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp") > $null
}
In the beginning, I did not have the -Path $PSScriptRoot and I ran my scripts using "Run with Powershell" in the context menu. This way the scripts seemed to use the scripts directory as the working directory. When I changed the default app to PowerShell, it started using PowerShell's install directory as the working directory. So I added -Path $PSScriptRoot and now the first script does not work; it opens a console and closes it right away. The second script works just fine.
Also, The first script still runs correctly if I "Run with Powershell" using the context menu.
What Am I doing wrong in the first script?
I am guessing that the path to your script has spaces in it. The problem has to do with the way windows passes the file argument to the application and the way the path to the script is being parsed.
When you run your script with "Run with Powershell", the following command is sent to the shell:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'C:\Temp\with spaces\scriptRoot.ps1'"
You'll notice that the command is wrapped in quotes. Which means that the shell will pass the quoted part as an argument to the powershell executable and effectively strips the outer " quotes. So powershell sees this as a command:
if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & 'C:\Temp\with spaces\scriptRoot.ps1'
Now you'll notice that the path to the script is still wrapped in single quotes and everything still works even though the path has spaces.
When you run the script as "Open with ..." powershell, windows (the explorer shell) will just pass the path to the script wrapped in quotes. It looks like this:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "C:\Temp\with spaces\scriptRoot.ps1"
If I open an command prompt and try to run the above command, I get the following:
#>"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "C:\Temp\with spaces\scriptRoot.ps1"
C:\Temp\with : The term 'C:\Temp\with' is not recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ C:\Temp\with spaces\scriptRoot.ps1
+ ~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Temp\with:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
That's because powershell itself never sees the outer quotes. If I add an extra layer of '' it works:
#>"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "'C:\Temp\with spaces\scriptRoot.ps1'"
C:\Temp\with spaces\scriptRoot.ps1
Unfortunately, I'm not sure how to fix this other than getting rid of the spaces in your path.

How to handle exit code for silent installation through PowerShell

I am using a silent installation command to install software. I am running this command from powershell using the below command:
$install= 'C:\PQR\Installation\xyz.ps1 -silentInstall -abcFolder C:\lmn -Folder C:\log -aRManagement -aHardening -rebootIfNeeded'
Invoke-expression $install
How to check if the installation is completed successfully or not. Please help me how to handle the above piece of installation.
Powershell scripts after execution return the status of execution, which is referred to as "return code" or "exit code". A successful script execution returns a 0 while an unsuccessful one returns a non-zero value that usually can be interpreted as an Error Code.
Use the command Exit $LASTEXITCODE at the end of the powershell script to return the error codes from the powershell script.
Example:
Powershell script for copying file to a folder
$dest ="C: est"
New-Item $dest -type directory -force
$source ="c:samplefile.txt"
Copy-Item $source $dest
exit $LASTEXITCODE

PowerShell execution fails due to space in directory name

I have been handed a PowerShell script that executes to install an executable which is placed in the same directory. This is packaged into SCCM (System Center Configuration Manager) for deployment. However, the deployment fails if during package deployment the temporary director where the package is pushed has a space anywhere in the file path.
For example:
C:\temp\deployment -- This will pass and the software will install.
C:\temp\deployment 1\ -- This will fail due to space between deployment and 1
C:\temporary directory\deployment\ -- This will fail due to space between temporary and directory.
Here is part of the code that I suspect is carrying out the installation:
if ($softwarename -eq "Not Installed") {
Write-Output "Installing softwarename..."
# Build a massive softwarename installation line
$tempFile = [System.IO.Path]::GetTempFileName() + ".cmd"
$installLine = "$scriptPath\softwarename.exe /s /v`"/qn INSTALLDIR=\`"C:\Program Files\directoryname\softwarename\`" AUTHTOKEN=REDACTED FULLCONSOLEADDRESS=$dest`:8413 HOSTNAME=$Env:COMPUTERNAME LOG_SOURCE_AUTO_CREATION_ENABLED=True LOG_SOURCE_AUTO_CREATION_PARAMETERS=`"`"Component1.AgentDevice=DeviceWindowsLog&Component1.Action=create&Component1.LogSourceName=$env:COMPUTERNAME&Component1.LogSourceIdentifier=$env:COMPUTERNAME&Component1.Log.Security=true&Component1.Filter.Security.Enabled=true&Component1.Filter.Security.Param=5156&Component1.Filter.Security.Type=Blacklist&Component1.Log.System=true&Component1.Log.Application=true&Component1.Log.DNS+Server=false&Component1.Log.File+Replication+Service=false&Component1.Log.Directory+Service=false&Component1.Destination.Name=$dest&Component1.RemoteMachinePollInterval=300&Component1.EventRateTuningProfile=High+Event+Rate+Server&Component1.MinLogsToProcessPerPass=1250&Component1.MaxLogsToProcessPerPass=1875`"`"`""
$installLine | Out-File -Encoding ascii -filepath $tempFile
Write-Output $tempFile
cmd.exe /c $tempFile
} else {
if ($psversiontable.psversion.major -gt 2) {
$props=ConvertFrom-StringData (Get-Content "c:\Program Files\directoryname\softwarename\config\install_config.txt" -Raw)
if ($props.ConfigurationServer -eq $dest) {
Write-Output "Configuration server is correct - no action"
} else {
Stop-Service (Get-Service -ErrorAction SilentlyContinue softwarename)
$props.ConfigurationServer=$dest
$props.StatusServer=$dest
$props.GetEnumerator() | % { "$($_.Name)=$($_.Value)" } | Out-File -Encoding ascii -filepath "c:\Program Files\directoryname\softwarename\config\install_config.txt"
del "c:\Program Files\directoryname\softwarename\config\ConfigurationServer.PEM"
Start-Service (Get-Service -ErrorAction SilentlyContinue softwarename)
Write-Output "Configuration server reset to $dest"
}
} else {
Write-Output "Powershell version does not support the reset functionality."
}
I suspect following two lines to be the cause of the problem:
$tempFile = [System.IO.Path]::GetTempFileName() + ".cmd"
$installLine = "$scriptPath\softwarename.exe /s /v`"/qn
I suspect that since execution will require exact path and a "space" in the directory path will lead to "path not found" (interestingly on my Windows 10 machine change directory [cd] command works without putting filepath with spaces in quotes - "" -- This leads me to believe I'm completely wrong, however at this point I have no other thing to look at).
Can anyone please assist on adding parameters to ensure that patch generated for .cmd file to be executed does not have a space?
I have tried to patch this by adding a batch file that copies the package to a static directory before execution. However, this is failing to deploy via SCCM.
In order to invoke a program from a batch file with a path that contains spaces, that path must be enclosed in "...":
# PowerShell string that constructs the command line to write to the batch file.
$installLine = "`"$scriptPath\softwarename.exe`" ..."
`" is how you embed a literal " in a "..." string in PowerShell.
By contrast, calling cd from a batch file with a path containing spaces also works without enclosing "...", but:
that is only possible without ambiguity because cd requires just one operand (the target dir.)
due to the inconsistency with how arguments with spaces must be handled by all other commands, supporting this was never a good idea to begin with.

How to pass parameters in PowerShell script which executes in WinSCP command?

I have a PowerShell script
cd "C:\Program Files (x86)\WinSCP"
. .\WinSCP.exe /console /script="L:\Work\SAS Data\FTP\local2remote.txt" /log=log.txt
It calls WinSCP command file
option batch on
open ftp://user:pass#host -passive=on
lcd "J:\Work\SAS Data\Roman\Lists"
put target.csv
exit
I want to convert "J:\Work\SAS Data\Roman\Lists" to a parameter in the PowerShell script where the parameter can be passed in the txt file. Same goes for the file target.csv. Any help appreciated.
The easiest way (and one that doesn't involve writing a temporary script file) would be to switch to /command for WinSCP:
# You don't need the . operator to run external programs
.\WinSCP.exe /console /log=log.txt /command `
'open ftp://user:pass#host -passive=on' `
'lcd "J:\Work\SAS Data\Roman\Lists"' `
'put target.csv' `
'exit'
Now you have a much easier time incorporating script parameters:
param([string] $Path, [string] $FileName)
& 'C:\Program Files (x86)\WinSCP\WinSCP.exe' /console /log=log.txt /command `
'open ftp://user:pass#host -passive=on' `
"lcd `"$Path`"" `
"put `"$FileName`"" `
'exit'
However, you can of course, still write a command file and pass that:
$script = Join-Path $Env:TEMP winscp-commands.txt
"open ftp://user:pass#host -passive=on
lcd ""$Path""
put ""$FileName""
exit" | Out-File -Encoding Default $script
& 'C:\Program Files (x86)\WinSCP\WinSCP.exe' /console /log=log.txt /script=$script
Remove-Item $script

How to run a Powershell script from the command line and pass a directory as a parameter

PowerShell -Command .\Foo.ps1
Foo.ps1:
Function Foo($directory)
{
echo $directory
}
if ($args.Length -eq 0)
{
echo "Usage: Foo <directory>"
}
else
{
Foo($args[0])
}
Despite Foo.ps1 being in the directory from where I am calling Powershell, this results in:
The term '.\Foo.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
EDIT: Wasn't working because PowerShell was changing directory due to profile.ps1 containing cd C:\
I then tried to call it specifying the full path to the script file, but no matter what I try, I can't get it to work. I believe I have to quote the path because it contains whitespaces, as does the file name I need to pass in an argument to the script.
Best guess so far:
PowerShell -Command "'C:\Dummy Directory 1\Foo.ps1' 'C:\Dummy Directory 2\File.txt'"
Outputs error:
Unexpected token 'C:\Dummy Directory 2\File.txt' in expression or statement.
At line:1 char:136.
try this:
powershell "C:\Dummy Directory 1\Foo.ps1 'C:\Dummy Directory 2\File.txt'"
you are calling a script file not a command so you have to use -file eg :
powershell -executionPolicy bypass -noexit -file "c:\temp\test.ps1" "c:\test with space"
for PS V2
powershell.exe -noexit &'c:\my scripts\test.ps1'
(check bottom of this technet page http://technet.microsoft.com/en-us/library/ee176949.aspx )
Using the flag -Command you can execute your entire powershell line as if it was a command in the PowerShell prompt:
powershell -Command "& '<PATH_TO_PS1_FILE>' '<ARG_1>' '<ARG_2>' ... '<ARG_N>'"
This solved my issue with running PowerShell commands in Visual Studio Post-Build and Pre-Build events.
Add the param declation at the top of ps1 file
test.ps1
param(
# Our preferred encoding
[parameter(Mandatory=$false)]
[ValidateSet("UTF8","Unicode","UTF7","ASCII","UTF32","BigEndianUnicode")]
[string]$Encoding = "UTF8"
)
write ("Encoding : {0}" -f $Encoding)
result
C:\temp> .\test.ps1 -Encoding ASCII
Encoding : ASCII
Change your code to the following :
Function Foo($directory)
{
echo $directory
}
if ($args.Length -eq 0)
{
echo "Usage: Foo <directory>"
}
else
{
Foo([string[]]$args)
}
And then invoke it as:
powershell -ExecutionPolicy RemoteSigned -File "c:\foo.ps1" "c:\Documents and Settings" "c:\test"
you have type and hit enter :
PowerShell -Command