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

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'

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.

Why doesn't this msiexec.exe command work in powershell?

I am trying to execute the following command through powershell, in a script invoked by an Advanced Installer generated installation program. The problem is that when the script executes, it chokes on a call to MSIEXEC.exe. More specifically, it puts up a windows dialog of the msiexec help screen.
Ok so maybe it doesn't like the way advanced installer is executing it. So I take the actual line that is causing problems:
msiexec.exe /q /i 'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\' ADDLOCAL='all'
And when I execute this directly in powershell, I still get the same stupid help screen. I have tried every conceivable variation of this command line:
/a and /passive instead of /i and /q
with double quotes
with single quotes
the msi unquoted
in an admin elevated shell
in a normal privilege shell
the msi located on the desktop instead of the temp folder
using /x to uninstall in case it was already installed
In all cases, I get the damnable "help" dialog. The only thing that appears to make a difference is if I leave off the INSTALLLOCATION and ADDLOCAL options. (These are apparently used as per "Unattended Installation part 2" found here: https://docs.mongodb.com/tutorials/install-mongodb-on-windows/). In that case it just exits quietly without installing anything.
I'm honestly at my wits' end having been beating my head against the wall on this all afternoon.
By the way, the reason I'm installing mongo in such an absurd way is I need a method of having a single-install system for my company's product. It depends on Mongo, and we have to have it run as a server and use authentication, so I have to have scripts to create the admin and database user and put it into authenticated mode. Since I needed to know where mongo was installed (to execute mongod.exe and mongo.exe) I need to query the user first for the location, then pass on the install location to the mongo installer. If I'm completely off the rails here please let me know that there's a better way.
Thanks
EDITED: I forgot to mention I wrote my complete powershell script and tested it before trying to execute it through advanced installer. The script worked until I tried to run it through the installer. Strange that I still can't execute the command though manually now.
It seems that in order to pass paths with embedded spaces to msiexec, you must use explicit embedded "..." quoting around them.
In your case, this means that instead of passing
INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\', you must pass INSTALLLOCATION='"C:\Program Files\MongoDB\Server\3.4\\"'[1]
Note the embedded "..." and the extra \ at the end of the path to ensure that \" alone isn't mistaken for an escaped " by msiexec (though it may work without the extra \ too).
To put it all together:
msiexec.exe /q /i `
'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' `
INSTALLLOCATION='"C:\Program Files\MongoDB\Server\3.4\\"' ADDLOCAL='all'
Caveat:
This embedded-quoting technique relies on longstanding, but broken PowerShell behavior - see this answer; should it ever get fixed, the technique will stop working; by contrast, the
--% approach shown below will continue to work.
A workaround-free, future-proof method is to use the PSv3+ ie helper function from the Native module (in PSv5+, install with Install-Module Native from the PowerShell Gallery), which internally compensates for all broken behavior and allows passing arguments as expected; that is, simply prepending ie to your original command would be enough:
# No workarounds needed with the 'ie' function from the 'Native' module.
ie msiexec.exe /q /i 'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\' ADDLOCAL='all'
The alternative is to stick with the original quoting and use --%, the stop-parsing symbol, but note that this means that you cannot use PowerShell variables in all subsequent arguments:
msiexec.exe /q /i `
'C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi' `
--% INSTALLLOCATION="C:\Program Files\MongoDB\Server\3.4\\" ADDLOCAL='all'
Note that msiexec, despite having a CLI (command-line interface), is a GUI-subsystem application, so it runs asynchronously by default; if you want to run it synchronously, use
Start-Process -Wait:
$msiArgs = '/q /i "C:\Users\ADMINI~1\AppData\Local\Temp\mongo-server-3.4-latest.msi" INSTALLLOCATION="C:\Program Files\MongoDB\Server\3.4\\" ADDLOCAL=all'
$ps = Start-Process -PassThru -Wait msiexec -ArgumentList $msiArgs
# $ps.ExitCode contains msiexec's exit code.
Note that the argument-list string, $msiArgs, is used as-is by Start-Process as part of the command line used to invoke the target program (msiexec), which means:
only (embedded) double-quoting must be used.
use "..." with embedded " escaped as `" to embed PowerShell variables and expressions in the string.
conversely, however, no workaround for partially quoted arguments is needed.
Even though Start-Process technically supports passing the arguments individually, as an array, this is best avoided due to a longstanding bug - see GitHub issue #5576.
[1] The reason that INSTALLLOCATION='C:\Program Files\MongoDB\Server\3.4\' doesn't work is that PowerShell transforms the argument by "..."-quoting it as a whole, which msiexec doesn't recognize; specifically, what is passed to msiexec in this case is:
"INSTALLLOCATION=C:\Program Files\MongoDB\Server\3.4\"

Error trying to pass in variables when calling MSI file

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.

My PowerShell script only works when running from ISE

I can't post all of the script contenet, but the basic idea is that it downloads JSON and converts it to objects using the ConvertFrom-Json cmdlet. Some objects are filtered out, and the rest are written to an XML/XLS document (in the Excel 2003 format). This file is then attached to an email and sent to various people.
The problem I'm having is that it only works when run from the Powershell ISE. Once I try setting up a scheduled task, calling it from cmd, or even calling it from powershell, the attached file is completely empty. It is as if some functions do not run (the one that loops through and creates all rows).
I can continue to run from ISE for the time being, but the idea of this script is to send out an automatic email that will require no intervention. Any ideas as to what could be causing this?
You need to run the script "dot sourced"
which can be done like this
powershell.exe -noexit -file c:\test.ps1
or
pwershell.exe -noexit ". c:\test.ps1"
See this link under the -File section
http://technet.microsoft.com/en-us/library/hh847736.aspx
Based on the answer from the comments to the original post, it seems that the script was (unexpectedly?) working in the ISE because of the bug/quirk/feature that allows scripts run in the ISE to be aware of variables used in the console window and vice-versa.
This can often cause logic problems or unexpected results when a script runs with a variable already defined (and not initialized carefully in the script).
Ways to avoid this problem:
Try to test your code in as clean an environment as possible.
http://powershell.com/cs/blogs/tips/archive/2015/02/12/getting-a-clean-powershell-environment.aspx
To make sure a script runs in a completely clean test environment, you
could of course restart the PowerShell ISE. A more convenient way is
to open another PowerShell tab: in the PowerShell ISE, choose File/New
PowerShell Tab.
Use Set-StrictMode 2 in your script to catch undefined variables, etc.
https://technet.microsoft.com/en-us/library/hh849692.aspx
Set-StrictMode -Version 2.0
Prohibits references to uninitialized variables (including uninitialized variables in strings).
Prohibits references to non-existent properties of an object.
Prohibits function calls that use the syntax for calling methods.
Prohibits a variable without a name (${}).
I have had this problem be for and for me executing the scrip using single-threaded function from powershell worked.
You could also try some other options, go to this link to find more info.
Example
powershell.exe -noexit c:\test.ps1 -sta
Change the missing variable or function to global.
function global:mycall{}
Start your Script with:
[cmdletbinding()]

How to get Hudson CI to execute a Powershell script?

I'm using Hudson version 1.324 for CI and have a couple of issues:
Environment:
Windows Server 2008
Powershell v1.0
Hudson 1.324 running as a service
Hudson Powershell Plugin installed
Psake (aka. "Powershell Make/Rake" available from Github) 0.23
(All current/latest versions as of this initial post)
I have a Powershell (PS) script that works to compile, run NUnit tests, and if successful, create a 7z file of the output. The PS script works from the command line, on both my local development box as well as the CI server where Hudson is installed.
1) Execution Policy with Powershell.
I initially ran a PS console on the server, ran Set-ExecutionPolicy Unrestricted, which allows any script to be run. (Yes, I realize the security concerns here, I'm trying to get something to work and Unrestricted should remove the security issues so I can focus on other problems.)
[This worked, and allowed me to fire off the PS build script from Hudson yesterday. I then encountered another problem, but we'll discuss that more in item #2.]
Once Hudson could fire off a PS script, it complained with the following error:
"C:\Windows\system32\WindowsPowerShell\v1.0\powershell "&
'OzSystems.Tools\psake\psake.ps1' '.\oz-build.ps1'" The term
'OzSystems.Tools\psake\psake.ps1' is not recognized as a cmdlet, funct
ion, operable program, or script file. Verify the term and try again.
At line:1 char:2
+ & <<<< 'OzSystems.Tools\psake\psake.ps1' '.\oz-build.ps1'"
Using the same command line, I am able to successfully execute the PS script from the command line manually. However Hudson is unable to get PS to do the same. After looking at additional PS documentation I also tried this:
"& 'OzSystems.Tools\psake\psake.ps1' '.\oz-build.ps1'"
and got a similar error. There does not appear to be any documentation for the Powershell plugin for Hudson. I've gone through all the Powershell plugin files and don't see anything that's configurable. I can't find a log file for Hudson to get additional information.
Can anyone help me past this?
2) I spent yesterday wrestling with #1. I came in this AM and tried to dig in again, after restarting the Hudson server/service, and now it appears that the ExecutionPolicy has been reset to Restricted. I did what worked yesterday, opened a PS console and Set-ExecutionPolicy to Unrestricted. It shows Unrestricted in the PS console, but Hudson says that it doesn't have rights to execution PS scripts. I reopened a new PS console and confirmed that the ExecutionPolicy is still Unrestriced -- it is. But Hudson evidently is not aware of this change. Restarting Hudson service again does not change Hudson's view of the policy.
Does anyone know what's going on here?
Thanks, Derek
I just ran into the problem of running powershell scripts in hudson. The thing is that you are running a 32-bit process of Java, and you've configured Hudson for 64-bit but not for 32-bit. See the following thread we created at microsoft.
http://social.technet.microsoft.com/Forums/en/winserverpowershell/thread/a9c08f7e-c557-46eb-b8a6-a19ba457e26d
If your lazy.
1. Start powershell (x86) from the start menu as administrator
2. Set the execution policy to remotesigned
Run this once and your homefree.
When Running PowerShell from a scheduled task or Hudson you want to:
Specify the -ExecutionPolicy parameter (in your case: -Ex Unrestricted)
Specify that command using either -Command { ... } or -File NOT BOTH and not without specifying which you mean.
Try this (except that I don't recommend using relative paths):
PowerShell.exe -Ex Unrestricted -Command "C:\Path\To\OzSystems.Tools\psake\psake.ps1" ".\oz-build.ps1"
To be clear, this will work too:
PowerShell.exe -Ex Unrestricted -Command "&{&'OzSystems.Tools\psake\psake.ps1' '.\oz-build.ps1'}"
The first string after -Command is interpreted as THE NAME OF A COMMAND, and every parameter after that is just passed to that command as a parameter. The string is NOT a script, it's the name of a command (in this case, a script file)... you cannot put "&'OzSystems.Tools\psake\psake.ps1'" but you can put "OzSystems.Tools\psake\psake.ps1" even if it has spaces.
To quote from the help (run PowerShell -?) emphasis mine:
-Command
Executes the specified commands (and any parameters) as though they were
typed at the Windows PowerShell command prompt, and then exits, unless
NoExit is specified. The value of Command can be "-", a string. or a
script block.
If the value of Command is "-", the command text is read from standard
input.
If the value of Command is a script block, the script block must be enclosed
in braces ({}). You can specify a script block only when running PowerShell.exe
in Windows PowerShell. The results of the script block are returned
to the parent shell as deserialized XML objects, not live objects.
If the value of Command is a string, Command must be the last parameter
in the command , because any characters typed after the command are
interpreted as the command arguments.
I have been having the same problems as you (as you've seen from my comments). I have given up on the powershell launcher and moved to running things using the batch file launcher. Even though I had set the system to unrestricted that setting didn't seem to matter to hudson's launcher. I don't know if it runs in some other context or something, even adding things to the global profile.ps1 didn't seem to help. What I ended up doing was running
powershell " set-executionpolicy Unrestricted; & 'somefile.ps1'"
which does what I need, although it isn't ideal. I've e-mailed the plugin author about this and will update.
For question #1, try this (assuming you are using PowerShell 2.0):
"C:\Windows\system32\WindowsPowerShell\v1.0\powershell -executionPolicy Unrestricted -file OzSystems.Tools\psake\psake.ps1 C:\{path}\oz-build.ps1"
You are using "." for the path to oz-build.ps1. I suspect you will need to provide the full path to your oz-build.ps1 file to make this work. Unless the infrastructure that executes the command above happens to have the current dir set correctly. And even if it is set correctly for the "process", that only matters to .NET/Win32 API calls and not to PowerShell cmdlets. Current dir in PowerShell is tracked differently than the process's current dir because PowerShell can have multiple runspaces running simultaneously. That sort of global, mutable value doesn't work in this concurrent scenario.
As for question #2, what account does the Hudson service run under? Make sure that account has executed Set-ExecutionPolicy RemoteSigned (or unrestricted).
I just got through this exact problem. What a pain!
If you are running a 32-bit JVM on a 64-bit Windows, make sure that you set the execution policy for the 32-bit Powershell interface. I found my 32 bit executable here:
C:\Windows\syswow64\Windowspowershell\v1.0\powerhsell.exe
The 32- and 64-bit Powershell environments are completely distinct so setting the execution policy in one has no effect on the other.