diff --git a/lib/PowerShell/System.Management.Automation.dll b/lib/PowerShell/System.Management.Automation.dll
new file mode 100644
index 0000000000..d4be277488
Binary files /dev/null and b/lib/PowerShell/System.Management.Automation.dll differ
diff --git a/src/chocolatey.console/chocolatey.console.csproj b/src/chocolatey.console/chocolatey.console.csproj
index 4c663d1e50..932a406ac6 100644
--- a/src/chocolatey.console/chocolatey.console.csproj
+++ b/src/chocolatey.console/chocolatey.console.csproj
@@ -96,6 +96,10 @@
+
+ False
+ ..\..\lib\PowerShell\System.Management.Automation.dll
+
diff --git a/src/chocolatey.tests.integration/context/badpackage/1.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/badpackage/1.0/tools/chocolateyInstall.ps1
index cbb52a9316..c701a6d30b 100644
--- a/src/chocolatey.tests.integration/context/badpackage/1.0/tools/chocolateyInstall.ps1
+++ b/src/chocolatey.tests.integration/context/badpackage/1.0/tools/chocolateyInstall.ps1
@@ -1,9 +1,11 @@
-$packageName = 'badpackage'
+try {
-try {
-
- Write-Host "Ya!"
- Write-Debug "A debug message"
+ Write-Output "This is $packageName v$packageVersion being installed to `n '$packageFolder'."
+ Write-Host "PowerShell Version is '$($PSVersionTable.PSVersion)' and CLR Version is '$($PSVersionTable.CLRVersion)'."
+ Write-Host "Execution Policy is '$(Get-ExecutionPolicy)'."
+ Write-Host "PSScriptRoot is '$PSScriptRoot'."
+ Write-Debug "A debug message."
+ Write-Verbose "Yo!"
Write-Warning "A warning!"
Write-Error "Oh no! An error"
throw "We had an error captain!"
diff --git a/src/chocolatey.tests.integration/context/badpackage/2.0/tools/chocolateyInstall.ps1 b/src/chocolatey.tests.integration/context/badpackage/2.0/tools/chocolateyInstall.ps1
index cbb52a9316..2e4b0d4244 100644
--- a/src/chocolatey.tests.integration/context/badpackage/2.0/tools/chocolateyInstall.ps1
+++ b/src/chocolatey.tests.integration/context/badpackage/2.0/tools/chocolateyInstall.ps1
@@ -1,9 +1,11 @@
-$packageName = 'badpackage'
+try {
-try {
-
- Write-Host "Ya!"
- Write-Debug "A debug message"
+ Write-Output "This is $packageName v$packageVersion being installed to `n '$packageFolder'."
+ Write-Host "PowerShell Version is '$($PSVersionTable.PSVersion)' and CLR Version is '$($PSVersionTable.CLRVersion)'."
+ Write-Host "Execution Policy is '$(Get-ExecutionPolicy)'."
+ Write-Host "PSScriptRoot is '$PSScriptRoot'."
+ Write-Debug "A debug message."
+ Write-Verbose "Yo!"
Write-Warning "A warning!"
Write-Error "Oh no! An error"
throw "We had an error captain!"
diff --git a/src/chocolatey.tests.integration/context/installpackage/1.0.0/tools/chocolateyinstall.ps1 b/src/chocolatey.tests.integration/context/installpackage/1.0.0/tools/chocolateyinstall.ps1
index 2491151c21..53b064d5b7 100644
--- a/src/chocolatey.tests.integration/context/installpackage/1.0.0/tools/chocolateyinstall.ps1
+++ b/src/chocolatey.tests.integration/context/installpackage/1.0.0/tools/chocolateyinstall.ps1
@@ -1,5 +1,10 @@
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
"simple file" | Out-File "$toolsDir\simplefile.txt" -force
-Write-Output "$env:PackageName $env:PackageVersion Installed"
+Write-Output "This is $packageName v$packageVersion being installed to `n $packageFolder"
+Write-Host "Ya!"
+Write-Debug "A debug message"
+Write-Verbose "Yo!"
+Write-Warning "A warning!"
+Write-Output "$packageName v$packageVersion has been installed to `n $packageFolder"
\ No newline at end of file
diff --git a/src/chocolatey/StringExtensions.cs b/src/chocolatey/StringExtensions.cs
index 62ab04ff7e..55c1f90528 100644
--- a/src/chocolatey/StringExtensions.cs
+++ b/src/chocolatey/StringExtensions.cs
@@ -17,6 +17,7 @@ namespace chocolatey
{
using System;
using System.Globalization;
+ using System.Security;
using System.Text.RegularExpressions;
using infrastructure.app;
using infrastructure.logging;
@@ -83,6 +84,26 @@ public static string to_string(this string input)
return input;
}
+ ///
+ /// Takes a string and returns a secure string
+ ///
+ /// The input.
+ ///
+ public static SecureString to_secure_string(this string input)
+ {
+ var secureString = new SecureString();
+
+ if (string.IsNullOrWhiteSpace(input)) return secureString;
+
+ foreach (char character in input)
+ {
+ secureString.AppendChar(character);
+ }
+
+ return secureString;
+ }
+
+
private static readonly Regex _spacePattern = new Regex(@"\s", RegexOptions.Compiled);
///
diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj
index 0cf1733747..59ce4fa0a4 100644
--- a/src/chocolatey/chocolatey.csproj
+++ b/src/chocolatey/chocolatey.csproj
@@ -58,6 +58,10 @@
+
+ False
+ ..\..\lib\PowerShell\System.Management.Automation.dll
+
..\packages\Rx-Core.2.1.30214.0\lib\Net40\System.Reactive.Core.dll
@@ -78,6 +82,8 @@
Properties\SolutionVersion.cs
+
+
@@ -116,6 +122,9 @@
+
+
+
@@ -203,6 +212,7 @@
+
@@ -288,4 +298,4 @@
-->
-
+
\ No newline at end of file
diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs
index f5e22c73de..2435440b31 100644
--- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs
+++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs
@@ -101,6 +101,7 @@ public static class Features
public static readonly string FailOnAutoUninstaller = "failOnAutoUninstaller";
public static readonly string AllowGlobalConfirmation = "allowGlobalConfirmation";
public static readonly string FailOnStandardError = "failOnStandardError";
+ public static readonly string UsePowerShellHost = "powershellHost";
}
public static class Messages
diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs
index 1bb651152c..8ea8a8fadc 100644
--- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs
+++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs
@@ -188,6 +188,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile
config.Features.AutoUninstaller = set_feature_flag(ApplicationParameters.Features.AutoUninstaller, configFileSettings, defaultEnabled: true, description: "Uninstall from programs and features without requiring an explicit uninstall script.");
config.Features.FailOnAutoUninstaller = set_feature_flag(ApplicationParameters.Features.FailOnAutoUninstaller, configFileSettings, defaultEnabled: false, description: "Fail if automatic uninstaller fails.");
config.Features.FailOnStandardError = set_feature_flag(ApplicationParameters.Features.FailOnStandardError, configFileSettings, defaultEnabled: false, description: "Fail if install provider writes to stderr.");
+ config.Features.UsePowerShellHost = set_feature_flag(ApplicationParameters.Features.UsePowerShellHost, configFileSettings, defaultEnabled: true, description: "Use Chocolatey's built-in PowerShell host.");
config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass.");
}
@@ -264,6 +265,9 @@ private static void set_global_options(IList args, ChocolateyConfigurati
.Add("failstderr|failonstderr|fail-on-stderr|fail-on-standard-error|fail-on-error-output",
"FailOnStandardError - Fail on standard error output (stderr), typically received when running external commands during install providers. This overrides the feature failOnStandardError.",
option => config.Features.FailOnStandardError = option != null)
+ .Add("use-system-powershell",
+ "UseSystemPowerShell - Execute PowerShell using an external process instead of the built-in PowerShell host.",
+ option => config.Features.UsePowerShellHost = option == null)
;
},
(unparsedArgs) =>
diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
index b1cc843e9c..495c70ce95 100644
--- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
+++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
@@ -336,6 +336,7 @@ public sealed class FeaturesConfiguration
public bool CheckSumFiles { get; set; }
public bool FailOnAutoUninstaller { get; set; }
public bool FailOnStandardError { get; set; }
+ public bool UsePowerShellHost { get; set; }
}
//todo: retrofit other command configs this way
diff --git a/src/chocolatey/infrastructure.app/services/PowershellService.cs b/src/chocolatey/infrastructure.app/services/PowershellService.cs
index 90c66b93db..140756815d 100644
--- a/src/chocolatey/infrastructure.app/services/PowershellService.cs
+++ b/src/chocolatey/infrastructure.app/services/PowershellService.cs
@@ -15,8 +15,12 @@
namespace chocolatey.infrastructure.app.services
{
+ using System;
using System.IO;
using System.Linq;
+ using System.Management.Automation;
+ using System.Management.Automation.Runspaces;
+ using System.Reflection;
using adapters;
using builders;
using commandline;
@@ -25,16 +29,16 @@ namespace chocolatey.infrastructure.app.services
using filesystem;
using infrastructure.commands;
using logging;
- using nuget;
+ using powershell;
using results;
+ using Assembly = adapters.Assembly;
+ using Console = System.Console;
using Environment = System.Environment;
public class PowershellService : IPowershellService
{
private readonly IFileSystem _fileSystem;
private readonly string _customImports;
- private const string OPERATION_COMPLETED_SUCCESSFULLY = "The operation completed successfully.";
- private const string INITIALIZE_DEFAULT_DRIVES = "Attempting to perform the InitializeDefaultDrives operation on the 'FileSystem' provider failed.";
public PowershellService(IFileSystem fileSystem)
: this(fileSystem, new CustomString(string.Empty))
@@ -234,63 +238,55 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack
if (shouldRun)
{
installerRun = true;
- var errorMessagesLogged = false;
- var exitCode = PowershellExecutor.execute(
- wrap_script_with_module(chocoPowerShellScript, configuration),
- _fileSystem,
- configuration.CommandExecutionTimeoutSeconds,
- (s, e) =>
- {
- if (string.IsNullOrWhiteSpace(e.Data)) return;
- //inspect for different streams
- if (e.Data.StartsWith("DEBUG:"))
- {
- this.Log().Debug(() => " " + e.Data);
- }
- else if (e.Data.StartsWith("WARNING:"))
- {
- this.Log().Warn(() => " " + e.Data);
- }
- else if (e.Data.StartsWith("VERBOSE:"))
- {
- this.Log().Info(ChocolateyLoggers.Verbose, () => " " + e.Data);
- }
- else
- {
- this.Log().Info(() => " " + e.Data);
- }
- },
- (s, e) =>
- {
- if (string.IsNullOrWhiteSpace(e.Data)) return;
- if (e.Data.is_equal_to(OPERATION_COMPLETED_SUCCESSFULLY) || e.Data.is_equal_to(INITIALIZE_DEFAULT_DRIVES))
- {
- this.Log().Info(() => " " + e.Data);
- }
- else
- {
- errorMessagesLogged = true;
- if (configuration.Features.FailOnStandardError) failure = true;
- this.Log().Error(() => " " + e.Data);
- }
- });
-
- if (exitCode != 0)
+
+ if (configuration.Features.UsePowerShellHost)
{
- failure = true;
+ add_assembly_resolver();
+ }
+
+ var result = new PowerShellExecutionResults
+ {
+ ExitCode = -1
+ };
+
+ try
+ {
+ result = configuration.Features.UsePowerShellHost
+ ? Execute.with_timeout(configuration.CommandExecutionTimeoutSeconds).command(() => run_host(configuration, chocoPowerShellScript), result)
+ : run_external_powershell(configuration, chocoPowerShellScript);
+ }
+ catch (Exception ex)
+ {
+ this.Log().Error(ex.Message);
+ result.ExitCode = -1;
}
- if (!configuration.Features.FailOnStandardError && errorMessagesLogged)
+ if (configuration.Features.UsePowerShellHost)
{
- this.Log().Warn(() =>
-@"Only an exit code of non-zero will fail the package by default. Set
+ remove_assembly_resolver();
+ }
+
+ if (result.StandardErrorWritten && configuration.Features.FailOnStandardError)
+ {
+ failure = true;
+ }
+ else if (result.StandardErrorWritten && result.ExitCode == 0)
+ {
+ this.Log().Warn(
+ () =>
+ @"Only an exit code of non-zero will fail the package by default. Set
`--failonstderr` if you want error messages to also fail a script. See
`choco -h` for details.");
}
+ if (result.ExitCode != 0)
+ {
+ failure = true;
+ }
+
if (failure)
{
- Environment.ExitCode = exitCode;
+ Environment.ExitCode = result.ExitCode;
packageResult.Messages.Add(new ResultMessage(ResultType.Error, "Error while running '{0}'.{1} See log for details.".format_with(powershellScript.FirstOrDefault(), Environment.NewLine)));
}
packageResult.Messages.Add(new ResultMessage(ResultType.Note, "Ran '{0}'".format_with(chocoPowerShellScript)));
@@ -299,5 +295,196 @@ public bool run_action(ChocolateyConfiguration configuration, PackageResult pack
return installerRun;
}
+
+ private class PowerShellExecutionResults
+ {
+ public int ExitCode { get; set; }
+ public bool StandardErrorWritten { get; set; }
+ }
+
+ private PowerShellExecutionResults run_external_powershell(ChocolateyConfiguration configuration, string chocoPowerShellScript)
+ {
+ var result = new PowerShellExecutionResults();
+ result.ExitCode = PowershellExecutor.execute(
+ wrap_script_with_module(chocoPowerShellScript, configuration),
+ _fileSystem,
+ configuration.CommandExecutionTimeoutSeconds,
+ (s, e) =>
+ {
+ if (string.IsNullOrWhiteSpace(e.Data)) return;
+ //inspect for different streams
+ if (e.Data.StartsWith("DEBUG:"))
+ {
+ this.Log().Debug(() => " " + e.Data);
+ }
+ else if (e.Data.StartsWith("WARNING:"))
+ {
+ this.Log().Warn(() => " " + e.Data);
+ }
+ else if (e.Data.StartsWith("VERBOSE:"))
+ {
+ this.Log().Info(ChocolateyLoggers.Verbose, () => " " + e.Data);
+ }
+ else
+ {
+ this.Log().Info(() => " " + e.Data);
+ }
+ },
+ (s, e) =>
+ {
+ if (string.IsNullOrWhiteSpace(e.Data)) return;
+ result.StandardErrorWritten = true;
+ this.Log().Error(() => " " + e.Data);
+ });
+
+ return result;
+ }
+
+ private ResolveEventHandler _handler = null;
+
+ private void add_assembly_resolver()
+ {
+ _handler = (sender, args) =>
+ {
+ var requestedAssembly = new AssemblyName(args.Name);
+
+ this.Log().Debug(ChocolateyLoggers.Verbose, "Redirecting {0}, requested by '{1}'".format_with(args.Name, args.RequestingAssembly == null ? string.Empty : args.RequestingAssembly.FullName));
+
+ AppDomain.CurrentDomain.AssemblyResolve -= _handler;
+
+ // we build against v1 - everything should update in a kosher manner to the newest, but it may not.
+ var assembly = attempt_version_load(requestedAssembly, new Version(5, 0, 0, 0)) ?? attempt_version_load(requestedAssembly, new Version(4, 0, 0, 0));
+ if (assembly == null) assembly = attempt_version_load(requestedAssembly, new Version(3, 0, 0, 0));
+ if (assembly == null) assembly = attempt_version_load(requestedAssembly, new Version(1, 0, 0, 0));
+
+ return assembly;
+ };
+
+ AppDomain.CurrentDomain.AssemblyResolve += _handler;
+ }
+
+ private System.Reflection.Assembly attempt_version_load(AssemblyName requestedAssembly, Version version)
+ {
+ if (requestedAssembly == null) return null;
+
+ requestedAssembly.Version = version;
+
+ try
+ {
+ return System.Reflection.Assembly.Load(requestedAssembly);
+ }
+ catch (Exception ex)
+ {
+ this.Log().Debug(ChocolateyLoggers.Verbose, "Attempting to load assembly {0} failed:{1} {2}".format_with(requestedAssembly.Name, Environment.NewLine, ex.Message));
+ return null;
+ }
+ }
+
+ private void remove_assembly_resolver()
+ {
+ if (_handler != null)
+ {
+ AppDomain.CurrentDomain.AssemblyResolve -= _handler;
+ }
+ }
+
+ private PowerShellExecutionResults run_host(ChocolateyConfiguration config, string chocoPowerShellScript)
+ {
+ var result = new PowerShellExecutionResults();
+ string commandToRun = wrap_script_with_module(chocoPowerShellScript, config);
+ var host = new PoshHost(config);
+ this.Log().Debug(() => "Calling built-in PowerShell host with ['{0}']".format_with(commandToRun.escape_curly_braces()));
+
+ var initialSessionState = InitialSessionState.CreateDefault();
+ // override system execution policy without accidentally setting it
+ initialSessionState.AuthorizationManager = new AuthorizationManager("choco");
+ using (var runspace = RunspaceFactory.CreateRunspace(host, initialSessionState))
+ {
+ runspace.Open();
+
+ // this will affect actual execution policy
+ //RunspaceInvoke invoker = new RunspaceInvoke(runspace);
+ //invoker.Invoke("Set-ExecutionPolicy ByPass");
+
+ using (var pipeline = runspace.CreatePipeline())
+ {
+ // The powershell host itself handles the following items:
+ // * Write-Debug
+ // * Write-Host
+ // * Write-Verbose
+ // * Write-Warning
+ //
+ // the two methods below will pick up Write-Output and Write-Error
+
+ // Write-Output
+ pipeline.Output.DataReady += (sender, args) =>
+ {
+ PipelineReader reader = sender as PipelineReader;
+
+ if (reader != null)
+ {
+ while (reader.Count > 0)
+ {
+ host.UI.WriteLine(reader.Read().to_string());
+ }
+ }
+ };
+
+ // Write-Error
+ pipeline.Error.DataReady += (sender, args) =>
+ {
+ PipelineReader