Error trying to pass in variables when calling MSI file - powershell

I am very new to using Powershell but keen to learn.
I am attempting to install a MSI package, using PowerShell and passing in some variables. The end result is for this to be an unattended installation deployed via Jenkins using PowerShell. Please keep in mind I have changed the port numbers for this example:
msiexec /i /quiet $SYSTEMID ="PC01" $PORT1 =0000 $PORT2 =0001 $TARGETDIR ="C:\Application\" "C:\MSIPackage64bit.msi"
When trying to run the above I get presented with a Windows ® Installer. pop up which lists a load of MSIExec variable options.
I have been looking on the web for quite some time and now believe I'm having issues due to my lack of understanding when it comes to PowerShell.

/I needs to be followed by the path to the MSI to be installed. Also get rid of the $ in front of the property names. Finally TARGETDIR isn't always TARGETDIR. Some MSIs are authored as INSTALLDIR, INSTALLLOCATION and other possible directory table entry names. Adding logging ( /l*v path_to_log ) is usually a good choice also.
PS- Please note that since you are doing a silent installation you need to either be installer per-user without need for elevation or you need to already be elevated.

Related

Running powershell script with CMake through Visual Studio without changing system-wide permissions

I want to zip up a folder. This is easy on operating systems like Linux where it's easy for the system admin to install the command line zip tool like apt install zip. Unfortunately on Windows it's not that straight forward.
A simple workaround I've found is that I can use Compress-Archive in a powershell script like this:
Compress-Archive -Path $folder -CompressionLevel Optimal -DestinationPath $zipPath
and then invoke this with the folder and zipPath before or after project building.
To do this, my CMakeLists.txt will have a line like:
execute_process(COMMAND powershell ${powershellScriptPath} "\"${folderPathToZip}\"" "\"${outputZipPath}\"" OUTPUT_QUIET)
This works fine, except I have to allow scripts to run globally and unsafely on my computer.
I want this to just work on any Windows computer without having to require the people to do any tinkering of settings. I also don't like the idea of getting other developers to allow scripts globally and unsafely for obvious reasons. I'd like my developers to be able to open the project and press build and it all works, and this is the last thing standing in my way from making it happen.
Is there a way to be able to get this script to run by changing the way the program is invoked? Or am I stuck and have to find another way to do this? Or is there some way to whitelist this specific file automatically without the user having to do any extra steps?
The only other hacky way around this would be for me to write my own zip utility that is invoked on every build to zip up the stuff, but that is a bunch of work for something that feels so close to being operational.
Edit: I can safely make the assumption that the computer has powershell installed and is relatively modern
You can bypass the effective PowerShell execution policy by passing -ExecutionPolicy Bypass to the PowerShell CLI (powershell.exe in the case of Windows PowerShell):
execute_process(COMMAND powershell -ExecutionPolicy Bypass -File ${powershellScriptPath} ${folderPathToZip} ${outputZipPath} OUTPUT_QUIET)
Also note that I'm using -File so as to instruct Windows PowerShell to execute a script file, because it defaults to -Command[1], which changes the interpretation of the arguments[2]; I'm not familiar with cmake, but the hope is that it provides double-quoting around the arguments by default / as necessary, which with -File should be sufficient.
[1] Note that PowerShell Core defaults to -File.
[2] For details, see the 2nd and subsequent sections of this answer.

Is there a way to prevent restart during .exe file installation?

I am trying to install .net framework 4.7.2 via PowerShell script.
The thing is the default restart that accompanies this .exe installation.
I am trying to prevent this restart but can't find the right arguments
for the job.
This is my current snippet:
Start-Process -FilePath "$path" -ArgumentList "/quiet" -Wait
$path is the dotnet .exe file.
I can't find documentation regards '-ArgumentList' options at all, or any efficient way to prevent this restart.
Any ideas?
I'd recommend giving the admin guide a read.
The switch you will need is /norestart. However, you may need to check if there is already a pending reboot on the system.

How to change default install path for Notepad++ in Powershell

I cant figure out the parameter(s) to change the default install path C:\Program Files\ of Notepad++ to the drive I want it to install to when I run my Powershell script. I am trying to do a silent install and can't change it manually. Does anyone know what parameters I can add to install the program to the E:\ drive? Below is my code and what I am trying in powershell. I have been messing around a lot with the -ArgumentList parameters to figure out if I can point it to the E:\ drive but no luck so far.
function install-Notepadpp()
{
$install="*PATH*\npp.6.6.2.Installer.exe"
Start-Process -FilePath $install -ArgumentList '/InstallDirectoryPath:"E:\"','/S' -Wait -Verb RunAs
Write-Host “Notepad++ has been installed.” -ForegroundColor Green}
##### This is Windows calling the function to install the script
install-Notepadpp
Do you really require an installation? You have the standalone package out there on dev site, you can copy it wherever you want. As for the 'cool' context menu addons, they are simple enough to add manually.
Below is the brief description of steps you need to take to get this done:
Have your Powershell copy the content of standalone package to the
desired folder.
Generate a new GUID using Powershell
[guid]::NewGUID()
Write down the GUID to variable (so you can reference it later).
Under HKEY_CLASSES_ROOT\CLSID\ add GUID entry in the same way the
rest of GUIDs are added.
Create a subcontainers InprocServer32 and Settings
In the InprocServer32 set the (Default) value to desired
Notepad++ installation path pointing directly to NppShell_06.dll (ex.
C:\Program Files\Notepad++\NppShell_06.dll)
Add ThreadingModel REG_SZ entry and set its' value to Apartment
In the Settings subcontainer, set the values accordingly - for a
list of valid values, please reference a machine with Notepad++
installed. The most "interesting" ones are "Path" and "Title"
Add the GUID entry you generated earlier to subcontainer "ANotepad++"
in HKEY_CLASSES_ROOT*\shellex\ContextMenuHandlers\ under (Default)
value
This should do it. Although I did not test the above on my machine, I am pretty confident that this will sort out the "manual" installation issue. As a sidenote, it could be worth as a suggestion to developer (or, as a best way, write it on your own!) to add some silent installation configuration switches so that we don't have to bother with the above 'workarounds'. Should you run into some issues setting this up, let me know.

How can I include KB2670838 in an installer with InstallShield 2013?

I'm using InstallShield 2013 to make a Basic MSI installer for an application that requires Windows Platform Update KB2670838.
For .NET frameworks and other requirements, I select them in InstallShield in the Redistributables section. KB2670838 is not available.
If I download KB2670838 from Microsoft I get a .msu file. Can that be included in the installer somehow so that it automatically installs if needed? If not, is there a way to stop the install and tell the user that "KB2670838 is required but not installed. Get it here..."?
In InstallShield, you should typically deliver this sort of update as a prerequisite (Tools > Prerequisite Editor), or as a package included in a Suite (reference [SystemFolder]wusa.exe to install an .msu file). In both cases this keeps the redistributable installation logically separate from your package's installation, while providing your users a single installer experience.
Glytzhkof mentions several really good points about how to determine whether the update has been installed. You will want to incorporate these into your conditions (on the prerequisite or suite package), and also into detecting the update or lack thereof in your .msi package so it can abort if the required update has not been installed by the time the .msi is launched.
The Add/Remove programs list in the registry could help you get a rough idea of what's installed:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
It seems this doesn't provide a full list of what is installed though: http://social.technet.microsoft.com/Forums/windows/en-US/d913471a-d7fb-448d-869b-da9025dcc943/where-does-addremove-programs-get-its-information-from-in-the-registry?forum=w7itprogeneral
Another way may be to use the file information from the knowledge base article:
http://support.microsoft.com/kb/2670838/en (For More Information : File Information) and use WIX / MSI's AppSearch / LaunchCondition feature. That should do the trick, though I find the syntax a bit counterintuitive.
Another approach is to write a custom action and combine these two sources (add /remove entry and file info). Such a custom action will make no changes to the system, and is hence less problematic than other custom actions that cause rollback-problems. I find it easier to test and maintain a custom action in case there are further prerequisites that are needed at some point. This is a matter of taste though. I just find it easier to run a prerequisite script against a selection of files to test that it identifies them correctly and run through as expected than to keep running the MSI file for every test.
Here is a similar question with some pointers from superuser.com:
https://superuser.com/questions/521175/determine-if-windows-hotfix-has-been-applied
And another link to serverfault.com (system administration site). Nice approach using PowerShell which can certainly be migrated to a custom action:
https://serverfault.com/questions/312778/determine-if-user-has-hotfix-981889-installed
Even more serverfault.com stuff involving update.exe, WMI and a Powershell script to view all installed hotfixes:
https://serverfault.com/questions/263847/how-can-i-query-my-system-via-command-line-to-see-if-a-kb-patch-is-installed . Recommended read. Microsoft: http://technet.microsoft.com/en-us/library/hh849836.aspx
PSInfo appears to be able to show installed hotfixes: http://technet.microsoft.com/en-us/sysinternals/bb897550
#Glytzhkof Good point. So how do I get InstallShield to abort and give the user a nice message so they know what to do? – shoelzer 1 hour ago
I will just add a new answer then - too long to write in a comment.
Locate the file details you need to scan for under "For More
Information : File Information" in this kdb article:
http://support.microsoft.com/kb/2670838/en
Select a few files to scan for and add as file searches in Installshield (see below screenshot). You specify a property for each file (FILE1FOUND, FILE2FOUND, FILE3FOUND, etc...), and if the search matches the file details (version, size, date, etc...) the property is set to the full path of the file. Otherwise the property is undefined or set to a default value (screenshot shows predefined search, and not file search, but you get the idea).
Finally you add LaunchCondition entries for each file to ensure that all files you have selected to check are the correct version or higher. I guess this is in Prerequisites or similar - I can't recall. Open the compiled MSI and check that it looks like the LaunchConditon table.
For the record: (not part of above suggestion)
Personally I am in favor of coding a single script for complex logic like this to ensure the logic can be inspected as a whole and crucially tested as a whole outside the MSI file. It is also good to add comments to such code to explain what the script is checking, and why (helps corporate deployment). A script can be run through dozens of tests against the machine directly without recompiling the MSI. This can save a lot of time if the logic is complex. If you write a compiled dll you can show a message box and attach the visual studio debugger to the msiexec.exe process (client or server depending on what context your custom action is running in) and step-through the code whilst embedded in the MSI, but this seems out of scope for your scenario. Just want to mention it for other people who might read this. Also check Stefan Kruger's installsite.com for more information on complex setup debugging like this.
It is important to note that scripting is never generally recommended for scenarios where the script makes changes to the system - if there is a built-in MSI way to achieve the same result. The reason for this is that a script that makes changes to a machine will need a separate rollback-operation to be specified for it for the MSI to follow best practice. This can be a spectacular amount of work and complexity to get right. The above script would only check system conditions, so there is no need for rollback support.
Let me try and add a reference style answer since my other answer is a bit organic to say the least at this point - I will leave it in since it contains an MSI discussion. See MSI recommendation in the middle section below:
WMI:
wmic qfe where "HotfixID = 'KB973687'"
PowerShell: (just get-hotfix for full list)
get-hotfix | findstr "981889"
SystemInfo (remove arguments for list format):
systeminfo /fo csv
PSInfo (seems to not list everything on all machines, and may not run silently properly):
PSinfo -h
Registry (apparently not complete list of hotfixes):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
For MSI custom action use, I would actually use a custom action that inspects file versions as explained in my other answer. Very reliable, and takes into account that the hotfix may be deprecated whilst the files are still up to date.
References:
Recommended: https://serverfault.com/questions/263847/how-can-i-query-my-system-via-command-line-to-see-if-a-kb-patch-is-installed
How do I get a list of installed updates and hotfixes?
SystemInfo: https://serverfault.com/questions/334552/how-to-check-that-a-known-windows-vulnerability-has-been-patched
http://windowsitpro.com/scripting/get-hotfix-information-quickly-wmic
https://serverfault.com/questions/69467/list-all-hotfixes-applied-to-windows-server
wmic in general: http://technet.microsoft.com/en-us/library/bb742610.aspx
Recommended: http://www.dedoimedo.com/computers/windows-wmic.html
http://www.techsupportalert.com/content/quick-and-easy-way-list-all-windows-updates-installed-your-system.htm
http://pario.no/2011/06/19/list-installed-windows-updates-using-wmic/
Had the same issue and solved it by adding a prerequisite of a PowerShell script and a batch file to execute it.
The pre.ps1 file looks something like this:
function TestConnection
{
Test-Connection -ComputerName "8.8.8.8" -Quiet
}
get-hotfix -id KB2670838
if(!$?){
#SourceURI = "https://download.microsoft.com/download/1/4/9/14936FE9-4D16-4019-A093-5E00182609EB/Windows6.1-KB2670838-x64.msu";
#$FileName = $SourceURI .Split('/')[-1]
#$BinPath = Join-Path $DownloadPath -ChildPath $FileName
Invoke-Webrequest -Uri $SourceURI -OutFile $BinPath
#Start-Process -FilePath $BinPath -ArgumentList "/q /norestart" -Wait -NoNewWindow
}
the pre.cmd file looks something like this:
#echo off
::set PS_FILE=%~dp0Prerequisite.ps1
set PS_FILE=%~dpn0.ps1
set PS_EXEC_PATH=%SystemRoot%\sysnative\WindowsPowerShell\v1.0\
set PS_EXEC_PATH=%SystemRoot%\System32\WindowsPowerShell\v1.0\
::set PS_EXEC_PATH=%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\
set PS_EXEC_PATH=
set PS_EXEC=%PS_EXEC_PATH%powershell.exe
echo %PS_EXEC%
echo %PS_FILE%
::%PS_EXEC% -file %PS_FILE% set-executionpolicy remotesigned
::%PS_EXEC% -NoProfile -ExecutionPolicy Bypass -Command "& '%PS_FILE%'"
::This is with admin rights
%PS_EXEC% -NoProfile -Command "& {Start-Process PowerShell.exe -ArgumentList '-NoProfile -ExecutionPolicy Bypass -File ""%PS_FILE%""' -Verb RunAs}"
::pause

How do I set a Machine environment variable from a Chocolatey package?

I'm the author of several Chocolatey packages that need to set environment variables as part of the proper "installation" of the package. For example: ANT_HOME, MW_HOME, and JAVA_HOME in the Java ecosystem should point to the installation directory for ant, weblogic, and java, respectively.
And, since Chocolatey is a machine-wide package manager, my thought would be to set these machine-wide (MSDN).
[System.Environment]::SetEnvironmentVariable("JAVA_HOME", "path/to/jre/install", "Machine")
If I just run that, I get the expected permission exception
Exception calling "SetEnvironmentVariable" with "3" argument(s): "Requested registry access is not allowed."
If I run the same command in an elevated prompt, manually, everything works fine. So, I need to invoke it in an elevated prompt. With Chocolatey, you're supposed to use: [Start-ChocolateyProcessAsAdmin][2] like so
Start-ChocolateyProcessAsAdmin #"
[System.Environment]::SetEnvironmentVariable("JAVA_HOME", "path/to/jre/install", "Machine")
"#
It prompts for elevation (I always run in a normal prompt when developing to be sure that it's actually working), but I see red errors flash by and this message with no warning/error
Elevating Permissions and running C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "& import-module -name 'C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1'; try{[System.Environment]::SetEnvironmentVariable("JAVA_HOME", "path/to/jre/install", "Machine"); start-sleep 6;}catch{write-error 'That was not sucessful';start-sleep
8;throw;}". This may take awhile, depending on the statements.
What gives?
UPDATE:
Some combination of the following makes it work, mysteriously... adding line-terminators (even to single statements) and wrapping all the arguments in single quotes.
Start-ChocolateyProcessAsAdmin #"
[System.Environment]::SetEnvironmentVariable('JAVA_HOME', 'path/to/jre/install', 'Machine');
"#
I would still like a non-brute-force answer. I'm sure it's a combination of PowerShell's nightmare string quoting/interpreting rules + Chocolatey's invocation.
UPDATE:
I'm also having trouble with multiple statements, even though I'm separating them!
Start-ChocolateyProcessAsAdmin #"
[System.Environment]::SetEnvironmentVariable('MW_HOME', 'path/to/wl/install', 'Machine');
[System.Environment]::SetEnvironmentVariable('WL_HOME', 'path/to/wl/install/wlserver', 'Machine');
"#
I saw, but did not capture, a Chocolatey log statement on screen that showed one of the line-terminators removed!
THOUGHT:
I mean, should I just not do this and write an easy "User" environment variable instead? It's not correct! The damn things are installed machine-wide... I don't want to give up yet.
Why not use the built in helper for this, Install-ChocolateyEnvironmentVariable. The usage is quite simple...
Install-ChocolateyEnvironmentVariable 'JAVA_HOME' 'path\to\jre' 'Machine'