Integrate changes between [SD:717473] and [SD:725290]

This commit is contained in:
PowerShell Team authored and Sergei Vorobev committed 2016-07-28 18:02:24 -07:00
1 parent 2280bea11e
commit 0dd425da19
28 files changed
+791 -285

No files matched your search

@@ -32,7 +32,6 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
using Directory = System.IO.Directory; using Directory = System.IO.Directory;
using ErrorCategory = PackageManagement.Internal.ErrorCategory; using ErrorCategory = PackageManagement.Internal.ErrorCategory;
using File = System.IO.File; using File = System.IO.File;
using System.IO.Compression;
public class BootstrapProvider { public class BootstrapProvider {
private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]> { private static readonly Dictionary<string, string[]> _features = new Dictionary<string, string[]> {
@@ -324,127 +323,12 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
return false; return false;
} }
case Iso19770_2.MediaType.NuGetPackage:
return InstallNugetPackage(provider, link, fastPath, request);
default: default:
request.Warning("Provider '{0}' with link '{1}' has unknown media type '{2}'.", provider.Name, link.HRef, link.MediaType); request.Warning("Provider '{0}' with link '{1}' has unknown media type '{2}'.", provider.Name, link.HRef, link.MediaType);
return false; return false;
} }
} }
private bool InstallNugetPackage(Package provider, Link link, string fastPath, BootstrapRequest request)
{
// download the nuget package
string downloadedNupkg = request.DownloadAndValidateFile(provider._swidtag);
if (downloadedNupkg != null)
{
// extracted folder
string extractedFolder = String.Concat(FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory());
try
{
//unzip the file
ZipFile.ExtractToDirectory(downloadedNupkg, extractedFolder);
if (Directory.Exists(extractedFolder))
{
string versionFolder = Path.Combine(request.DestinationPath(request), provider.Name, provider.Version);
// tool folder is where we find things like nuget.exe
string toolFolder = Path.Combine(extractedFolder, "tools");
string libFolder = Path.Combine(extractedFolder, "lib");
// create the directory version folder if not exist
if (!Directory.Exists(versionFolder))
{
Directory.CreateDirectory(versionFolder);
}
// copy the tools directory
if (Directory.Exists(toolFolder))
{
string destinationToolFolder = Path.Combine(versionFolder, "tools");
if (!Directory.Exists(destinationToolFolder))
{
Directory.CreateDirectory(destinationToolFolder);
}
foreach (string child in Directory.EnumerateFiles(toolFolder))
{
try
{
// try copy and overwrite
File.Copy(child, Path.Combine(destinationToolFolder, Path.GetFileName(child)), true);
}
catch (Exception e)
{
request.Debug(e.StackTrace);
if (!(e is UnauthorizedAccessException || e is IOException))
{
// something wrong, delete the version folder
versionFolder.TryHardToDelete();
return false;
}
// otherwise this means the file is just being used. so just moves on to copy other files
}
}
}
// copy files from lib
if (Directory.Exists(libFolder))
{
// check that the lib folder has at most 1 dll
if (Directory.EnumerateFiles(libFolder).Count(file => String.Equals(Path.GetExtension(file), ".dll", StringComparison.OrdinalIgnoreCase)) > 1)
{
request.Warning(String.Format(CultureInfo.CurrentCulture, Resources.Messages.MoreThanOneDllExists, provider.Name));
return false;
}
foreach (string child in Directory.EnumerateFiles(libFolder))
{
try
{
File.Copy(child, Path.Combine(versionFolder, Path.GetFileName(child)), true);
}
catch (Exception e)
{
request.Debug(e.StackTrace);
if (!(e is UnauthorizedAccessException || e is IOException))
{
// something wrong, delete the version folder
versionFolder.TryHardToDelete();
return false;
}
// otherwise this means the file is just being used. so just moves on to copy other files
}
}
}
// target file name is the assembly provider
string targetFile = Path.Combine(versionFolder, Path.GetFileName(link.Attributes[Iso19770_2.Discovery.TargetFilename]));
if (File.Exists(targetFile))
{
request.Verbose(Resources.Messages.InstalledPackage, provider.Name, targetFile);
request.YieldFromSwidtag(provider, fastPath);
return true;
}
}
}
finally
{
downloadedNupkg.TryHardToDelete();
extractedFolder.TryHardToDelete();
}
}
return false;
}
private bool InstallPackageFile(Package provider, string fastPath, BootstrapRequest request) { private bool InstallPackageFile(Package provider, string fastPath, BootstrapRequest request) {
// we can download and verify this package and get the core to install it. // we can download and verify this package and get the core to install it.
var file = request.DownloadAndValidateFile(provider._swidtag); var file = request.DownloadAndValidateFile(provider._swidtag);
@@ -522,7 +406,6 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
return false; return false;
} }
string targetFilename = fastPath; string targetFilename = fastPath;
string file = fastPath; string file = fastPath;
@@ -551,8 +434,15 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
//... providername\version\.dll //... providername\version\.dll
var versionFolder = Path.Combine(request.DestinationPath(request), provider.Name, provider.Version); var versionFolder = Path.Combine(request.DestinationPath(request), provider.Name, provider.Version);
if (!Directory.Exists(versionFolder)) { // if version folder exists, remove it
//we create it if (Directory.Exists(versionFolder))
{
RemoveDirectory(versionFolder);
}
// create the directory if we successfully deleted it
if (!Directory.Exists(versionFolder))
{
Directory.CreateDirectory(versionFolder); Directory.CreateDirectory(versionFolder);
} }
@@ -561,12 +451,6 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
if (file != null) { if (file != null) {
try try
{ {
// looks good! let's keep it
if (File.Exists(targetFile)) {
request.Debug("Removing old file '{0}'", targetFile);
targetFile.TryHardToDelete();
}
// is that file still there? // is that file still there?
if (File.Exists(targetFile)) { if (File.Exists(targetFile)) {
request.Error(ErrorCategory.InvalidOperation, fastPath, Constants.Messages.UnableToRemoveFile, targetFile); request.Error(ErrorCategory.InvalidOperation, fastPath, Constants.Messages.UnableToRemoveFile, targetFile);
@@ -575,8 +459,17 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
request.Debug("Copying file '{0}' to '{1}'", file, targetFile); request.Debug("Copying file '{0}' to '{1}'", file, targetFile);
try { try {
if (File.Exists(file))
{
// if this is a file
File.Copy(file, targetFile); File.Copy(file, targetFile);
} }
else if (Directory.Exists(file))
{
// if this is a directory, copy items over
CopyDirectory(file, versionFolder);
}
}
catch (Exception ex) { catch (Exception ex) {
request.Debug(ex.StackTrace); request.Debug(ex.StackTrace);
return false; return false;
@@ -604,6 +497,56 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
return false; return false;
} }
private void RemoveDirectory(string directoryFolder)
{
// remove all files
foreach (var fileToBeRemoved in Directory.EnumerateFiles(directoryFolder))
{
fileToBeRemoved.TryHardToDelete();
}
// remove all subdirectories
foreach (var folderToBeRemoved in Directory.EnumerateDirectories(directoryFolder))
{
RemoveDirectory(folderToBeRemoved);
}
try
{
// now try to remove the directory
Directory.Delete(directoryFolder);
}
catch { }
}
private void CopyDirectory(string sourceFolder, string destinationFolder)
{
// check that source and destination folders exist
if (!sourceFolder.DirectoryExists() || !destinationFolder.DirectoryExists())
{
return;
}
// copy the files over
foreach (var file in Directory.EnumerateFiles(sourceFolder))
{
File.Copy(file, Path.Combine(destinationFolder, Path.GetFileName(file)), true);
}
// copy the directories over
foreach (var directory in Directory.EnumerateDirectories(sourceFolder))
{
var destinationDirName = Path.Combine(destinationFolder, Path.GetFileName(directory));
if (!Directory.Exists(destinationDirName))
{
Directory.CreateDirectory(destinationDirName);
}
CopyDirectory(directory, destinationDirName);
}
}
private void InstallPackageFromFile(string fastPath, BootstrapRequest request) private void InstallPackageFromFile(string fastPath, BootstrapRequest request)
{ {
var filePath = new Uri(fastPath).LocalPath; var filePath = new Uri(fastPath).LocalPath;
@@ -29,6 +29,9 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
using PackageManagement.Internal.Utility.Collections; using PackageManagement.Internal.Utility.Collections;
using PackageManagement.Internal.Utility.Extensions; using PackageManagement.Internal.Utility.Extensions;
using ErrorCategory = PackageManagement.Internal.ErrorCategory; using ErrorCategory = PackageManagement.Internal.ErrorCategory;
using System.IO.Compression;
using File = System.IO.File;
using Directory = System.IO.Directory;
public abstract class BootstrapRequest : Request { public abstract class BootstrapRequest : Request {
internal Uri[] _urls internal Uri[] _urls
@@ -201,6 +204,51 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
} }
} }
/// <summary>
/// Extract zipped package and return the unzipped folder
/// </summary>
/// <param name="zippedPackagePath"></param>
/// <returns></returns>
private string ExtractZipPackage(string zippedPackagePath)
{
if (zippedPackagePath != null && zippedPackagePath.FileExists())
{
// extracted folder
string extractedFolder = FilesystemExtensions.GenerateTemporaryFileOrDirectoryNameInTempDirectory();
try
{
//unzip the file
ZipFile.ExtractToDirectory(zippedPackagePath, extractedFolder);
// extraction fails
if (!Directory.Exists(extractedFolder))
{
Verbose(string.Format(CultureInfo.CurrentCulture, Resources.Messages.FailToExtract, zippedPackagePath, extractedFolder));
return string.Empty;
}
// the zipped folder
var zippedDirectory = Directory.EnumerateDirectories(extractedFolder).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(zippedDirectory) && Directory.Exists(zippedDirectory))
{
return zippedDirectory;
}
}
catch (Exception ex)
{
Verbose(string.Format(CultureInfo.CurrentCulture, Resources.Messages.FailToInstallZipFolder, zippedPackagePath, ex.Message));
Debug(ex.StackTrace);
// remove the extracted folder
extractedFolder.TryHardToDelete();
}
}
return string.Empty;
}
/// <summary> /// <summary>
/// Helper function to retry downloading a file. /// Helper function to retry downloading a file.
/// downloadFileFunction is the main function that is used to download the file when given a uri /// downloadFileFunction is the main function that is used to download the file when given a uri
@@ -258,6 +306,21 @@ namespace Microsoft.PackageManagement.Providers.Internal.Bootstrap {
// got a valid file! // got a valid file!
if (file != null && file.FileExists()) { if (file != null && file.FileExists()) {
// if file is zip, unpack it and return the unpacked folder
if (link.MediaType == Iso19770_2.MediaType.ZipPackage)
{
try
{
// let's extract the zipped file
return ExtractZipPackage(file);
}
finally
{
// delete the zipped file
file.TryHardToDelete();
}
}
return file; return file;
} }
} }
@@ -121,6 +121,16 @@
<value>Fail to extract and copy to '{0}'.</value> <value>Fail to extract and copy to '{0}'.</value>
<comment>0 - The directory we are copying to</comment> <comment>0 - The directory we are copying to</comment>
</data> </data>
<data name="FailToDeleteExistingFolder" xml:space="preserve">
<value>Cannot delete existing provider folder '{0}'.</value>
</data>
<data name="FailToExtract" xml:space="preserve">
<value>Cannot extract from '{0}' to '{1}'</value>
</data>
<data name="FailToInstallZipFolder" xml:space="preserve">
<value>Fail to extract zip package '{0}': '{1}'</value>
<comment>0 - package path, 1 - exception message</comment>
</data>
<data name="FindingPackage" xml:space="preserve"> <data name="FindingPackage" xml:space="preserve">
<value>Finding the package '{0}'.</value> <value>Finding the package '{0}'.</value>
<comment>0 - package name</comment> <comment>0 - package name</comment>
@@ -240,7 +240,17 @@
var partialManifestNameMatches = GetPackageFiles(partialManifestName).Where( var partialManifestNameMatches = GetPackageFiles(partialManifestName).Where(
path => FileNameMatchesPattern(packageId, version, path)); path => FileNameMatchesPattern(packageId, version, path));
return filesMatchingFullName.Concat(partialNameMatches).Concat(partialManifestNameMatches); filesMatchingFullName = filesMatchingFullName.Concat(partialNameMatches).Concat(partialManifestNameMatches);
}
// cannot find matching files, we should try to search for just packageid.nupkg
if (filesMatchingFullName.Count() == 0)
{
// exclude version
var packageWithoutVersionName = FileUtility.MakePackageFileName(true, packageId, null, NuGetConstant.PackageExtension);
var packageWithoutVersionManifest = Path.ChangeExtension(packageWithoutVersionName, NuGetConstant.ManifestExtension);
return GetPackageFiles(packageWithoutVersionName).Concat(GetPackageFiles(packageWithoutVersionManifest));
} }
return filesMatchingFullName; return filesMatchingFullName;
@@ -44,10 +44,8 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
request.Verbose("Package: '{0}'", exePackage); request.Verbose("Package: '{0}'", exePackage);
// validate the file // validate the file
if (!WebDownloader.PerformSecurityScan(exePackage)) if (!WebDownloader.VerifyHash(exePackage,package, request))
{ {
//TODO Security Scan work
request.WriteError(ErrorCategory.InvalidOperation, Constants.ProviderName, "The package download from '{0}' failed in the security scan.", package.Source);
return false; return false;
} }
@@ -180,6 +180,11 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
{ {
item.Common = common; item.Common = common;
item.FilePath = packageSpecPath; item.FilePath = packageSpecPath;
if (string.IsNullOrWhiteSpace(item.Version))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Messages.VersionNotFound, item.Name));
}
} }
item.PackageSource = packageSource; item.PackageSource = packageSource;
@@ -201,6 +206,11 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
else else
{ {
convertedPackage.Common = new CommonDefinition(); convertedPackage.Common = new CommonDefinition();
if (String.IsNullOrWhiteSpace(convertedPackage.Version))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Messages.VersionNotFound, convertedPackage.Name, packageSpecPath));
}
} }
convertedPackage.FilePath = packageSpecPath; convertedPackage.FilePath = packageSpecPath;
@@ -249,6 +259,13 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
public List<string> installationOption { get; set; } public List<string> installationOption { get; set; }
} }
public class PackageHash
{
public string algorithm { get; set; }
public string hashCode { get; set; }
}
public class CommonDefinition public class CommonDefinition
{ {
public string Name { get; set; } public string Name { get; set; }
@@ -291,10 +308,14 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
private bool _isPackageProvider; private bool _isPackageProvider;
private string _installArguments; private string _installArguments;
private string _unInstallAdditionalArguments; private string _unInstallAdditionalArguments;
private string _source;
public CommonDefinition Common { get; set; } public CommonDefinition Common { get; set; }
public string Version { get; set; } public string Version { get; set; }
public PackageHash Hash { get; set; }
public SemanticVersion SemVer public SemanticVersion SemVer
{ {
get get
@@ -306,7 +327,36 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
return null; return null;
} }
} }
public string Source { get; set; } public string Source
{
get
{
return _source;
}
set
{
if (value != null)
{
Uri uri;
// check whether source is http instead of https
if (!Uri.TryCreate(value, UriKind.Absolute, out uri))
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Messages.UnsuportedUriFormat, Constants.ProviderName, value));
}
if (!uri.IsFile)
{
if (uri.Scheme != Uri.UriSchemeHttps)
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Messages.UriSchemeNotSupported, uri.Scheme, Uri.UriSchemeHttps));
}
}
_source = value;
}
}
}
public string Name public string Name
{ {
@@ -22,10 +22,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Management.Automation; using System.Management.Automation;
using Microsoft.PackageManagement.Provider.Utility; using Microsoft.PackageManagement.Provider.Utility;
using PackageManagement.Packaging; using PackageManagement.Packaging;
using ErrorCategory = PackageManagement.Internal.ErrorCategory; using ErrorCategory = PackageManagement.Internal.ErrorCategory;
using System.Security.Cryptography;
public class PackageSourceListProvider { public class PackageSourceListProvider {
@@ -37,6 +39,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
private const string RequiredPackageManagementVersion = "1.0.0.1"; private const string RequiredPackageManagementVersion = "1.0.0.1";
private static bool _doesPackageManagementVersionMatch = false; private static bool _doesPackageManagementVersionMatch = false;
private static Version _currentPackageManagementVersion; private static Version _currentPackageManagementVersion;
private static string _pslDirLocation = Path.Combine(Environment.GetEnvironmentVariable("appdata"), Constants.ProviderName);
/// <summary> /// <summary>
/// The features that this package supports. /// The features that this package supports.
@@ -125,6 +128,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
case "install": case "install":
request.YieldDynamicOption("Scope", "String", false, new[] {"CurrentUser", "AllUsers"}); request.YieldDynamicOption("Scope", "String", false, new[] {"CurrentUser", "AllUsers"});
request.YieldDynamicOption("SkipHashValidation", Constants.OptionType.Switch, false);
break; break;
} }
} }
@@ -189,23 +193,16 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
return; return;
} }
request.Debug(Resources.Messages.DebugInfoCallMethod, PackageProviderName, "GetOptionValue"); // Set-PackageSource will update the existing package source. In that case IsUpdate = true.
// if this is supposed to be an update, there will be a dynamic parameter set for IsUpdatePackageSource
var isUpdate = request.GetOptionValue(Constants.Parameters.IsUpdate).IsTrue(); var isUpdate = request.GetOptionValue(Constants.Parameters.IsUpdate).IsTrue();
request.Debug(Resources.Messages.VariableCheck, "IsUpdate", isUpdate); request.Debug(Resources.Messages.VariableCheck, "IsUpdate", isUpdate);
// if your source supports credentials you get get them too:
// string username =request.Username;
// SecureString password = request.Password;
// feel free to send back an error here if your provider requires credentials for package sources.
// check first that we're not clobbering an existing source, unless this is an update // check first that we're not clobbering an existing source, unless this is an update
request.Debug(Resources.Messages.DebugInfoCallMethod, PackageProviderName, string.Format(CultureInfo.InvariantCulture, "FindRegisteredSource -name'{0}'", name)); request.Debug(Resources.Messages.DebugInfoCallMethod, PackageProviderName, string.Format(CultureInfo.InvariantCulture, "FindRegisteredSource -name'{0}'", name));
var src = request.FindRegisteredSource(name); var src = request.FindRegisteredSource(name);
if (src != null && !isUpdate) { if (src != null && !isUpdate) {
// tell the user that there's one here already // tell the user that there's one here already
request.WriteError(ErrorCategory.InvalidArgument, name, Constants.Messages.PackageSourceExists, name); request.WriteError(ErrorCategory.InvalidArgument, name, Constants.Messages.PackageSourceExists, name);
@@ -213,29 +210,72 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
} }
// conversely, if it didn't find one, and it is an update, that's bad too: // conversely, if it didn't find one, and it is an update, that's bad too:
if (src == null && isUpdate) { if (src == null && isUpdate)
{
// you can't find that package source? Tell that to the user // you can't find that package source? Tell that to the user
request.WriteError(ErrorCategory.ObjectNotFound, name, Constants.Messages.UnableToResolveSource, Constants.ProviderName, name); request.WriteError(ErrorCategory.ObjectNotFound, name, Constants.Messages.UnableToResolveSource, name);
return; return;
} }
// ok, we know that we're ok to save this source // ok, we know that we're ok to save this source
// next we check if the location is valid (if we support that kind of thing) // next we check if the location is valid (if we support that kind of thing)
var validated = false; var validated = false;
if (!request.SkipValidate.Value) {
// the user has not opted to skip validating the package source location, so check if the source is valid
validated = request.ValidateSourceLocation(location); validated = request.ValidateSourceLocation(location);
if (!validated) { if (!validated) {
request.WriteError(ErrorCategory.InvalidData, name, Constants.Messages.SourceLocationNotValid, location); request.WriteError(ErrorCategory.InvalidData, name, Constants.Messages.SourceLocationNotValid, location);
return; return;
} }
else
{
request.Verbose(Resources.Messages.SuccessfullyValidated, name); request.Verbose(Resources.Messages.SuccessfullyValidated, name);
} }
bool force = request.GetOptionValue("Force") != null;
//if source is UNC location/ copy it to local path;
Uri uri;
if (Uri.TryCreate(location, UriKind.Absolute, out uri))
{
if (uri.IsFile && uri.IsUnc)
{
string fileName = Path.GetFileNameWithoutExtension(location);
string directory = Path.GetDirectoryName(location);
string catalogFilePath = Path.Combine(directory, fileName+".cat");
if (!File.Exists(catalogFilePath))
{
request.WriteError(ErrorCategory.InvalidData, location, Resources.Messages.CatalogFileMissing, location);
return;
}
if (!TestCatalogFile(location, catalogFilePath, request))
{
return;
}
if (force || request.ShouldContinue(Resources.Messages.QueryDownloadPackageSourceList.format(location), Resources.Messages.PackageSourceListNotTrusted))
{
string destination = Path.Combine(_pslDirLocation, Path.GetFileName(uri.LocalPath));
if (File.Exists(destination))
{
if (force || request.ShouldContinue(Resources.Messages.OverwriteFile, Resources.Messages.FileExists))
{
File.Copy(location, destination, true);
location = destination;
}
else
{
return;
}
}
else {
File.Copy(location, destination);
location = destination;
}
}
else
{
return;
}
}
}
// it's good to check just before you actaully write something to see if the user has cancelled the operation // it's good to check just before you actaully write something to see if the user has cancelled the operation
if (request.IsCanceled) { if (request.IsCanceled) {
return; return;
@@ -320,7 +360,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
{ {
// if version is specified then name can not be empty or with wildcard. in this case the cmdlet has been errored out already. // if version is specified then name can not be empty or with wildcard. in this case the cmdlet has been errored out already.
// here we just return all packages we can find // here we just return all packages we can find
if (request.FilterOnVersion(packages, requiredVersion, minimumVersion, maximumVersion, minInclusive: true, maxInclusive: true, latest: false).Any(p => !request.YieldFromSwidtag(p, p.Name))) if (request.FilterOnVersion(packages, requiredVersion, minimumVersion, maximumVersion, minInclusive: true, maxInclusive: true, latest: false).OrderBy(p => p.Name).Any(p => !request.YieldFromSwidtag(p, p.Name)))
{ {
return; return;
} }
@@ -329,7 +369,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
//return the latest version //return the latest version
if (packages.GroupBy(p => p.Name) if (packages.GroupBy(p => p.Name)
.Select(each => each.OrderByDescending(pp => pp.Version).FirstOrDefault()).Any( item =>!request.YieldFromSwidtag(item, item.Name))) .Select(each => each.OrderByDescending(pp => pp.Version).FirstOrDefault()).OrderBy(p=>p.Name).Any( item =>!request.YieldFromSwidtag(item, item.Name)))
{ {
return; return;
} }
@@ -722,15 +762,14 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
{ {
return; return;
} }
// validate the file // validate the file
if (!WebDownloader.PerformSecurityScan(destination)) if (!WebDownloader.VerifyHash(destination,package, request))
{ {
//TODO Security Scan work
request.WriteError(ErrorCategory.InvalidOperation, Constants.ProviderName, "The package download from '{0}' failed in the security scan.", package.Source);
return; return;
} }
if (!package.IsTrustedSource) if (!package.IsTrustedSource)
{ {
if (!request.ShouldContinueWithUntrustedPackageSource(package.Name, package.Source)) if (!request.ShouldContinueWithUntrustedPackageSource(package.Name, package.Source))
@@ -869,6 +908,37 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
return Enumerable.Empty<PSModuleInfo>(); return Enumerable.Empty<PSModuleInfo>();
} }
internal static bool TestCatalogFile(string jsonFile, string catalogFile, PackageSourceListRequest request)
{
try
{
PSObject result = null;
using (PowerShell powershell = PowerShell.Create())
{
if (powershell != null)
{
result = powershell
.AddCommand("Test-FileCatalog")
.AddParameter("CatalogFilePath", catalogFile)
.AddParameter("Path", jsonFile)
.Invoke().FirstOrDefault();
}
if (result.ToString().EqualsIgnoreCase("Valid"))
return true;
}
}
catch(Exception ex)
{
request.WriteError(ErrorCategory.InvalidData, catalogFile, Resources.Messages.CatalogFileVerificationFailedWithError, catalogFile, ex.Message.ToString());
return false;
}
request.WriteError(ErrorCategory.InvalidData, catalogFile, Resources.Messages.CatalogFileVerificationFailed, jsonFile);
return false;
}
} }
} }
@@ -43,6 +43,10 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
private string _configurationFileLocation; private string _configurationFileLocation;
private XDocument _config; private XDocument _config;
private string _defaultConfig; private string _defaultConfig;
private string PowerShellSourceURL = @"http://go.microsoft.com/fwlink/?LinkID=821777&clcid=0x409";
private string PowerShellNanoSourceURL = @"http://go.microsoft.com/fwlink/?LinkID=821783&clcid=0x409";
private string PowerShellSourceCatalogURL = @"http://go.microsoft.com/fwlink/?LinkID=823093&clcid=0x409";
private string PowerShellNanoSourceCatalogURL = @"http://go.microsoft.com/fwlink/?LinkID=823094&clcid=0x409";
private IEnumerable<string> _packageSources; private IEnumerable<string> _packageSources;
private const string _PackageSourceListRequest = "PackageSourceListRequest"; private const string _PackageSourceListRequest = "PackageSourceListRequest";
private HttpClient _httpClient; private HttpClient _httpClient;
@@ -57,6 +61,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
internal Lazy<bool> SkipValidate; internal Lazy<bool> SkipValidate;
internal readonly Lazy<bool> AllVersions; internal readonly Lazy<bool> AllVersions;
internal readonly Lazy<string[]> Headers; internal readonly Lazy<string[]> Headers;
internal Lazy<bool> SkipHashValidation;
internal const WildcardOptions WildcardOptions = System.Management.Automation.WildcardOptions.CultureInvariant | System.Management.Automation.WildcardOptions.IgnoreCase; internal const WildcardOptions WildcardOptions = System.Management.Automation.WildcardOptions.CultureInvariant | System.Management.Automation.WildcardOptions.IgnoreCase;
@@ -67,6 +72,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
</packageSources> </packageSources>
</configuration>"; </configuration>";
internal const string EmptyConfig = @"<?xml version=""1.0""?>
<configuration>
<packageSources>
</packageSources>
</configuration>";
/// <summary> /// <summary>
/// Ctor required by the PackageManagement Platform /// Ctor required by the PackageManagement Platform
/// </summary> /// </summary>
@@ -75,20 +86,48 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
AllVersions = new Lazy<bool>(() => GetOptionValue("AllVersions").IsTrue()); AllVersions = new Lazy<bool>(() => GetOptionValue("AllVersions").IsTrue());
SkipValidate = new Lazy<bool>(() => GetOptionValue("SkipValidate").IsTrue()); SkipValidate = new Lazy<bool>(() => GetOptionValue("SkipValidate").IsTrue());
Headers = new Lazy<string[]>(() => (GetOptionValues("Headers") ?? new string[0]).ToArray()); Headers = new Lazy<string[]>(() => (GetOptionValues("Headers") ?? new string[0]).ToArray());
SkipHashValidation = new Lazy<bool>(() => GetOptionValue("SkipHashValidation").IsTrue());
} }
internal string DefaultConfigLocation
internal string DefaultJSONFileLocation
{
get
{
return Path.Combine(Environment.GetEnvironmentVariable("appdata"), Constants.ProviderName, Constants.JSONFileName);
}
}
internal string DefaultCatlogFileLocation
{
get
{
return Path.Combine(Environment.GetEnvironmentVariable("appdata"), Constants.ProviderName, Constants.CatFileName);
}
}
internal string DefaultJSONSourceLocation
{ {
get get
{ {
#if CORECLR #if CORECLR
return Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles"), "PackageManagement\\ProviderAssemblies\\PSL", "PSL.json"); return PowerShellNanoSourceURL;
#else #else
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "PackageManagement\\ProviderAssemblies\\PSL", "PSL.json"); return PowerShellSourceURL;
#endif #endif
} }
} }
internal string DefaultJSONCatalogFileLocation
{
get
{
#if CORECLR
return PowerShellNanoSourceCatalogURL;
#else
return PowerShellSourceCatalogURL;
#endif
}
}
internal string DefaultConfig internal string DefaultConfig
{ {
@@ -96,13 +135,13 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
{ {
if (_defaultConfig == null) if (_defaultConfig == null)
{ {
_defaultConfig = DefaultConfigDefinition.Replace("##", DefaultConfigLocation); _defaultConfig = DefaultConfigDefinition.Replace("##", DefaultJSONFileLocation);
} }
return _defaultConfig; return _defaultConfig;
} }
} }
internal IEnumerable<string> PackageSources internal IEnumerable<string> PackageSources
{ {
get get
@@ -790,66 +829,23 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
Debug(Resources.Messages.NotFoundRegisteredSource, src, Constants.ProviderName); Debug(Resources.Messages.NotFoundRegisteredSource, src, Constants.ProviderName);
// doesn't look like we have this as a source.
if (Uri.IsWellFormedUriString(src, UriKind.Absolute))
{
// we have been passed in an URI
var srcUri = new Uri(src);
if (SupportedSchemes.Contains(srcUri.Scheme.ToLower()))
{
// it's one of our supported uri types.
var isValidated = false;
//if (!SkipValidate.Value)
//{
// isValidated = PathUtility.ValidateSourceUri(SupportedSchemes, srcUri, this);
//}
if (SkipValidate.Value || isValidated)
{
Debug(Resources.Messages.SuccessfullyValidated, src);
yield return new PackageSource
{
Request = this,
Location = srcUri.ToString(),
Name = srcUri.ToString(),
Trusted = false,
IsRegistered = false,
IsValidated = isValidated,
};
continue;
}
if (userSpecifiesArrayOfSources)
{
Verbose(Constants.Messages.UnableToResolveSource, Constants.ProviderName, src);
}
else
{
Warning(Constants.Messages.UnableToResolveSource, Constants.ProviderName, src);
}
continue;
}
// Not a valid location?
if (userSpecifiesArrayOfSources)
{
Verbose(Constants.Messages.UnableToResolveSource, Constants.ProviderName, src);
}
else
{
Warning(Constants.Messages.UnableToResolveSource, Constants.ProviderName, src);
}
continue;
}
// is it a file path? // is it a file path?
if (System.IO.Directory.Exists(src)) if (System.IO.Directory.Exists(src))
{ {
Debug(Resources.Messages.SourceIsADirectory, src); Debug(Resources.Messages.SourceIsADirectory, src);
PackageSource newSource = new PackageSource
{
Request = this,
Location = src,
Name = src,
Trusted = true,
IsRegistered = false,
IsValidated = true,
};
yield return newSource;
}
else if (File.Exists(src) && (Path.GetExtension(src).EqualsIgnoreCase(".json")) )
{
PackageSource newSource = new PackageSource PackageSource newSource = new PackageSource
{ {
Request = this, Request = this,
@@ -947,22 +943,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
{ {
get get
{ {
if (String.IsNullOrWhiteSpace(_configurationFileLocation)) if (string.IsNullOrWhiteSpace(_configurationFileLocation))
{
// get the value from the request
var path = GetOptionValue("ConfigFile");
if (!String.IsNullOrWhiteSpace(path))
{
_configurationFileLocation = path;
if (!System.IO.File.Exists(_configurationFileLocation))
{
WriteError(Internal.ErrorCategory.InvalidArgument, _configurationFileLocation, Resources.Messages.FileNotFound);
}
}
else
{ {
var appdataFolder = Environment.GetEnvironmentVariable("appdata"); var appdataFolder = Environment.GetEnvironmentVariable("appdata");
_configurationFileLocation = Path.Combine(appdataFolder, Constants.ProviderName, Constants.SettingsFileName); _configurationFileLocation = Path.Combine(appdataFolder, Constants.ProviderName, Constants.SettingsFileName);
@@ -978,16 +959,36 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
if (!System.IO.File.Exists(_configurationFileLocation)) if (!System.IO.File.Exists(_configurationFileLocation))
{ {
Debug(Resources.Messages.CreateFile, _configurationFileLocation); Debug(Resources.Messages.CreateFile, _configurationFileLocation);
System.IO.File.WriteAllText(_configurationFileLocation, DefaultConfig); bool addDefaultConfig = false;
if (System.IO.File.Exists(DefaultJSONFileLocation))
{
addDefaultConfig = true;
}
else
{
bool force = this.GetOptionValue("Force") != null;
if (force || this.ShouldContinue(Resources.Messages.QueryDownloadPackageSourceList.format(DefaultJSONSourceLocation), Resources.Messages.PackageSourceListNotFound.format(DefaultJSONFileLocation)))
{
WebDownloader.DownloadFile(DefaultJSONSourceLocation, DefaultJSONFileLocation, this, null);
WebDownloader.DownloadFile(DefaultJSONCatalogFileLocation, DefaultCatlogFileLocation, this, null);
if (System.IO.File.Exists(DefaultJSONFileLocation) && System.IO.File.Exists(DefaultCatlogFileLocation) &&
PackageSourceListProvider.TestCatalogFile(DefaultJSONFileLocation, DefaultCatlogFileLocation, this))
{
addDefaultConfig = true;
} }
} }
} }
if(addDefaultConfig)
System.IO.File.WriteAllText(_configurationFileLocation, DefaultConfig);
else
System.IO.File.WriteAllText(_configurationFileLocation, EmptyConfig);
}
}
return _configurationFileLocation; return _configurationFileLocation;
} }
} }
internal bool WriteError(Internal.ErrorCategory category, string targetObjectValue, string messageText, params object[] args) internal bool WriteError(Internal.ErrorCategory category, string targetObjectValue, string messageText, params object[] args)
{ {
return Error(messageText, category.ToString(), targetObjectValue, base.FormatMessageString(messageText, args)); return Error(messageText, category.ToString(), targetObjectValue, base.FormatMessageString(messageText, args));
@@ -1001,10 +1002,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
internal bool ValidateSourceLocation(string location) internal bool ValidateSourceLocation(string location)
{ {
Debug(Resources.Messages.DebugInfoCallMethod3, _PackageSourceListRequest, "ValidateSourceLocation", location); Debug(Resources.Messages.DebugInfoCallMethod3, _PackageSourceListRequest, "ValidateSourceLocation", location);
if (File.Exists(location) && (Path.GetExtension(location).EqualsIgnoreCase(".json")))
//TODO this is needed for register-packagesource {
return true; return true;
} }
return false;
}
internal static IRequest ExtendRequest(Dictionary<string, string[]> options, string[] sources, bool isTrusted, PackageSourceListRequest request) { internal static IRequest ExtendRequest(Dictionary<string, string[]> options, string[] sources, bool isTrusted, PackageSourceListRequest request) {
var srcs = sources ?? new List<string>().ToArray(); var srcs = sources ?? new List<string>().ToArray();
@@ -43,6 +43,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
throw new ArgumentNullException("packageSource"); throw new ArgumentNullException("packageSource");
} }
if(string.IsNullOrWhiteSpace(packageSource.Location) || !System.IO.File.Exists(packageSource.Location))
{
request.Warning(Resources.Messages.PackageSourceManifestNotFound, packageSource.Location, Constants.ProviderName);
return;
}
Uri uri; Uri uri;
if (Uri.TryCreate(packageSource.Location, UriKind.Absolute, out uri)) if (Uri.TryCreate(packageSource.Location, UriKind.Absolute, out uri))
@@ -56,6 +62,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
} }
catch (Exception ex) catch (Exception ex)
{ {
request.Warning(ex.Message);
while (ex.InnerException != null)
{
ex = ex.InnerException;
request.Warning(ex.Message);
}
request.Warning(string.Format(CultureInfo.CurrentCulture, Resources.Messages.InvalidPackageListFormat, uri.AbsolutePath)); request.Warning(string.Format(CultureInfo.CurrentCulture, Resources.Messages.InvalidPackageListFormat, uri.AbsolutePath));
ex.Dump(request); ex.Dump(request);
} }
@@ -82,6 +94,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
} }
catch (Exception ex) catch (Exception ex)
{ {
request.Warning(ex.Message);
while (ex.InnerException != null)
{
ex = ex.InnerException;
request.Warning(ex.Message);
}
request.Warning(string.Format(CultureInfo.CurrentCulture, Resources.Messages.InvalidPackageListFormat, file)); request.Warning(string.Format(CultureInfo.CurrentCulture, Resources.Messages.InvalidPackageListFormat, file));
ex.Dump(request); ex.Dump(request);
} }
@@ -25,8 +25,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
using File = System.IO.File; using File = System.IO.File;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.PackageManagement.Provider.Utility; using Microsoft.PackageManagement.Provider.Utility;
using System.Security.Cryptography;
using System.Linq;
using ErrorCategory = PackageManagement.Internal.ErrorCategory;
public class WebDownloader { public class WebDownloader
{
/// <summary> /// <summary>
/// Download data from remote via uri query. /// Download data from remote via uri query.
@@ -83,8 +87,11 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
// here we read content that we got from the http response stream into the bytes array // here we read content that we got from the http response stream into the bytes array
current = await input.ReadAsync(bytes, 0, bytes.Length, cts.Token); current = await input.ReadAsync(bytes, 0, bytes.Length, cts.Token);
int progressPercentage = progressTracker.StartPercent;
// report initial progress // report initial progress
request.Progress(progressTracker.ProgressID, progressTracker.StartPercent, Resources.Messages.BytesRead, current); request.Progress(progressTracker.ProgressID, progressPercentage, Resources.Messages.BytesRead, current);
int i = progressTracker.StartPercent;
while (current > 0) while (current > 0)
{ {
@@ -94,7 +101,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
await output.WriteAsync(bytes, 0, current, cts.Token); await output.WriteAsync(bytes, 0, current, cts.Token);
// report the progress // report the progress
request.Progress(progressTracker.ProgressID, progressTracker.StartPercent, Resources.Messages.BytesRead, totalDownloaded); request.Progress(progressTracker.ProgressID, progressPercentage<progressTracker.EndPercent?progressPercentage++:progressTracker.EndPercent, Resources.Messages.BytesRead, totalDownloaded);
// continue reading from the stream // continue reading from the stream
current = await input.ReadAsync(bytes, 0, bytes.Length, cts.Token); current = await input.ReadAsync(bytes, 0, bytes.Length, cts.Token);
@@ -183,7 +190,6 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
progressTracker = new ProgressTracker(request.StartProgress(0, Resources.Messages.DownloadingPackage, queryUrl)); progressTracker = new ProgressTracker(request.StartProgress(0, Resources.Messages.DownloadingPackage, queryUrl));
} }
Uri uri; Uri uri;
if (!Uri.TryCreate(queryUrl, UriKind.Absolute, out uri)) if (!Uri.TryCreate(queryUrl, UriKind.Absolute, out uri))
@@ -233,9 +239,64 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
} }
} }
internal static bool PerformSecurityScan(string fileFullPath) internal static bool VerifyHash(string fileFullPath,PackageJson package, PackageSourceListRequest request)
{ {
//TODO need to do security scan before running install //skip in case the skip switch is specified
if (request.SkipHashValidation.Value)
{
request.Verbose(Resources.Messages.SkipHashValidation);
return true;
}
PackageHash packageHash = package.Hash;
if (packageHash==null || string.IsNullOrWhiteSpace(packageHash.algorithm) || string.IsNullOrWhiteSpace(packageHash.hashCode))
{
request.WriteError(ErrorCategory.InvalidArgument, Constants.ProviderName, Resources.Messages.HashNotSpecified, package.Name);
return false;
}
try
{
HashAlgorithm hashAlgorithm = null;
switch (packageHash.algorithm.ToLowerInvariant())
{
case "sha256":
hashAlgorithm = SHA256.Create();
break;
case "md5":
hashAlgorithm = MD5.Create();
break;
case "sha512":
hashAlgorithm = SHA512.Create();
break;
default:
request.WriteError(ErrorCategory.InvalidArgument, Constants.ProviderName, Resources.Messages.InvalidHashAlgorithm, packageHash.algorithm);
return false;
}
using (FileStream stream = File.OpenRead(fileFullPath))
{
// compute the hash
byte[] computedHash = hashAlgorithm.ComputeHash(stream);
// convert the original hash we got from json
byte[] hashFromJSON = Convert.FromBase64String(package.Hash.hashCode);
if (!Enumerable.SequenceEqual(computedHash, hashFromJSON))
{
request.WriteError(ErrorCategory.InvalidOperation, Constants.ProviderName, Resources.Messages.HashVerificationFailed, package.Name, package.Source);
return false;
}
else
{
request.Verbose(Resources.Messages.HashValidationSuccessfull);
}
}
}
catch
{
request.WriteError(ErrorCategory.InvalidOperation, Constants.ProviderName, Resources.Messages.HashVerificationFailed, package.Name, package.Source);
return false;
}
return true; return true;
} }
} }
@@ -54,10 +54,8 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
} }
// validate the file // validate the file
if (!WebDownloader.PerformSecurityScan(file)) if (!WebDownloader.VerifyHash(file, package,request))
{ {
//TODO Security Scan work
request.WriteError(ErrorCategory.InvalidOperation, Constants.ProviderName, "The package download from '{0}' failed in the security scan.", package.Source);
return false; return false;
} }
@@ -19,6 +19,12 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
/// </summary> /// </summary>
public static readonly string SettingsFileName = "PSL.config"; public static readonly string SettingsFileName = "PSL.config";
/// <summary>
/// Sample JSON file containing open powershell entry
/// </summary>
public static readonly string JSONFileName = "PSL.json";
public static readonly string CatFileName = "PSL.cat";
internal static class MediaType internal static class MediaType
{ {
public const string MsiPackage = "msi"; public const string MsiPackage = "msi";
@@ -79,7 +85,7 @@ namespace Microsoft.PackageManagement.PackageSourceListProvider
public const string ProviderSwidtagUnavailable = "MSG:ProviderSwidtagUnavailable"; public const string ProviderSwidtagUnavailable = "MSG:ProviderSwidtagUnavailable";
public const string RemoveEnvironmentVariableRequiresElevation = "MSG:RemoveEnvironmentVariableRequiresElevation"; public const string RemoveEnvironmentVariableRequiresElevation = "MSG:RemoveEnvironmentVariableRequiresElevation";
public const string SchemeNotSupported = "MSG:SchemeNotSupported"; public const string SchemeNotSupported = "MSG:SchemeNotSupported";
public const string SourceLocationNotValid = "MSG:SourceLocationNotValid"; public const string SourceLocationNotValid = "MSG:SourceLocationNotValid_Location";
public const string UnableToCopyFileTo = "MSG:UnableToCopyFileTo"; public const string UnableToCopyFileTo = "MSG:UnableToCopyFileTo";
public const string UnableToCreateShortcutTargetDoesNotExist = "MSG:UnableToCreateShortcutTargetDoesNotExist"; public const string UnableToCreateShortcutTargetDoesNotExist = "MSG:UnableToCreateShortcutTargetDoesNotExist";
public const string UnableToDownload = "MSG:UnableToDownload"; public const string UnableToDownload = "MSG:UnableToDownload";
@@ -277,7 +277,7 @@
<value>Unknown category for '{0}'::'{1}': '{2}'</value> <value>Unknown category for '{0}'::'{1}': '{2}'</value>
</data> </data>
<data name="UriSchemeNotSupported" xml:space="preserve"> <data name="UriSchemeNotSupported" xml:space="preserve">
<value>Uri Scheme '{0}' is not supported.</value> <value>Uri Scheme '{0}' is not supported. Only '{1}' is supported.</value>
</data> </data>
<data name="UseDefaultConfig" xml:space="preserve"> <data name="UseDefaultConfig" xml:space="preserve">
<value>Use the default configuration.</value> <value>Use the default configuration.</value>
@@ -392,4 +392,50 @@
<data name="InvalidPackageListFormat" xml:space="preserve"> <data name="InvalidPackageListFormat" xml:space="preserve">
<value>Package List Source '{0}' has incorrect format.</value> <value>Package List Source '{0}' has incorrect format.</value>
</data> </data>
<data name="QueryDownloadPackageSourceList" xml:space="preserve">
<value>Do you want to download package source list from '{0}'?</value>
<comment>0 - source list path</comment>
</data>
<data name="FileExists" xml:space="preserve">
<value>A file with this name already exists in target directory</value>
</data>
<data name="OverwriteFile" xml:space="preserve">
<value>Do you want to overwrite it?</value>
</data>
<data name="PackageSourceListNotTrusted" xml:space="preserve">
<value>PackageSourceList is not trusted</value>
</data>
<data name="VersionNotFound" xml:space="preserve">
<value>Package '{0}' in json file '{1}' is missing version information.</value>
</data>
<data name="HashNotSpecified" xml:space="preserve">
<value>Invalid hash for package '{0}'. Please correct the values in json file or use -SkipHashValidation switch to skip Hash validation.</value>
</data>
<data name="HashValidationSuccessfull" xml:space="preserve">
<value>Hash validation was successful.</value>
</data>
<data name="HashVerificationFailed" xml:space="preserve">
<value>Hash verification failed for package '{0}' downloaded from '{1}'.</value>
</data>
<data name="InvalidHashAlgorithm" xml:space="preserve">
<value>Hash algorithm '{0}' is not supported. We only support sha512, md5 and sha256.</value>
</data>
<data name="SkipHashValidation" xml:space="preserve">
<value>Skipping Hash Validation.</value>
</data>
<data name="CatalogFileMissing" xml:space="preserve">
<value>Catalog file not found for '{0}'. Catalog files are required for non local json files and should have the same name and located in the same directory as json file.</value>
</data>
<data name="CatalogFileVerificationFailed" xml:space="preserve">
<value>Catalog File verification failed for '{0}'.</value>
</data>
<data name="CatalogFileVerificationFailedWithError" xml:space="preserve">
<value>Catalog File '{0}' verification failed with '{1}'.</value>
</data>
<data name="PackageSourceListNotFound" xml:space="preserve">
<value>Cannot find source list file '{0}'.</value>
</data>
<data name="PackageSourceManifestNotFound" xml:space="preserve">
<value>File '{0}' is registered as a package source location for the provider '{1}'. But it does not exist. Run 'Get-PackageSource' to view the registered package sources, 'Unregister-PackageSource' to unregister the source if you do not wish to use it or 'Set-PackageSource' to fix the source location.</value>
</data>
</root> </root>
@@ -198,6 +198,43 @@ namespace Microsoft.PowerShell.Workflow
/// based on the definition provided by this script block. /// based on the definition provided by this script block.
/// </returns> /// </returns>
public List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, out ParseException parsingErrors, string rootWorkflowName) public List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, out ParseException parsingErrors, string rootWorkflowName)
{
return CompileWorkflowsImpl(ast, definingModule, initialSessionState, null, out parsingErrors, rootWorkflowName);
}
/// <summary>
/// Converts a PowerShell AST into a script block that represents
/// the workflow to run.
/// </summary>
/// <param name="ast">The PowerShell AST correpsponding to the job's definition.</param>
/// <param name="definingModule">The module that is defining this command (if any).</param>
/// <param name="initialSessionState">The initial session state of a runspace.</param>
/// <param name="sourceLanguageMode">Language mode of source that is creating the workflow.</param>
/// <param name="parsingErrors">Optional, once assigned, only root Workflow will be compiled.</param>
/// <returns>
/// A PowerShell script block that invokes an underlying job,
/// based on the definition provided by this script block.
/// </returns>
public List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, PSLanguageMode? sourceLanguageMode, out ParseException parsingErrors)
{
return CompileWorkflowsImpl(ast, definingModule, initialSessionState, sourceLanguageMode, out parsingErrors, null);
}
/// <summary>
/// Converts a PowerShell AST into a script block that represents
/// the workflow to run.
/// </summary>
/// <param name="ast">The PowerShell AST correpsponding to the job's definition.</param>
/// <param name="definingModule">The module that is defining this command (if any)</param>
/// <param name="initialSessionState">The initial session state of a runspace.</param>
/// <param name="sourceLanguageMode">Language mode of source that is creating the workflow.</param>
/// <param name="parsingErrors">parsing errors</param>
/// <param name="rootWorkflowName">Optional, once assigned, only root Workflow will be compiled</param>
/// <returns>
/// A PowerShell script block that invokes an underlying job,
/// based on the definition provided by this script block.
/// </returns>
private List<WorkflowInfo> CompileWorkflowsImpl(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, PSLanguageMode? sourceLanguageMode, out ParseException parsingErrors, string rootWorkflowName)
{ {
List<ParseError> errorList = new List<ParseError>(); List<ParseError> errorList = new List<ParseError>();
@@ -327,7 +364,7 @@ namespace Microsoft.PowerShell.Workflow
try try
{ {
entry.workflowInfo = CompileSingleWorkflow(entry.scope, func, scriptBlockTokenCache, definingModule, requiredAssemblies, activityMap, processedActivityLibraries, invoker, rootWorkflowName); entry.workflowInfo = CompileSingleWorkflow(entry.scope, func, scriptBlockTokenCache, definingModule, requiredAssemblies, activityMap, processedActivityLibraries, invoker, sourceLanguageMode, rootWorkflowName);
result.Add(entry.workflowInfo); result.Add(entry.workflowInfo);
} }
catch (ParseException e) catch (ParseException e)
@@ -365,6 +402,7 @@ namespace Microsoft.PowerShell.Workflow
Dictionary<string, Type> activityMap, Dictionary<string, Type> activityMap,
HashSet<string> processedActivityLibraries, HashSet<string> processedActivityLibraries,
System.Management.Automation.PowerShell invoker, System.Management.Automation.PowerShell invoker,
PSLanguageMode? sourceLanguageMode = (PSLanguageMode?)null,
string rootWorkflowName = null) string rootWorkflowName = null)
{ {
Dictionary<string, ParameterAst> parameterValidation; Dictionary<string, ParameterAst> parameterValidation;
@@ -399,10 +437,11 @@ namespace Microsoft.PowerShell.Workflow
// Pass either the workflow script file path if available or the full source otherwise. // Pass either the workflow script file path if available or the full source otherwise.
string scriptFile = parentAst.Extent.File; string scriptFile = parentAst.Extent.File;
string scriptSource = string.IsNullOrEmpty(scriptFile) ? parentAst.Extent.StartScriptPosition.GetFullScript() : null; string scriptSource = string.IsNullOrEmpty(scriptFile) ? parentAst.Extent.StartScriptPosition.GetFullScript() : null;
ReadOnlyCollection<AttributeAst> attributeAstCollection = (func.Body.ParamBlock != null) ? func.Body.ParamBlock.Attributes : null;
var functionDefinition = ImportWorkflowCommand.CreateFunctionFromXaml(func.Name, xaml, var functionDefinition = ImportWorkflowCommand.CreateFunctionFromXaml(func.Name, xaml,
referencedAssemblies, calledWorkflows.Select(wfi => wfi.NestedXamlDefinition).ToArray(), referencedAssemblies, calledWorkflows.Select(wfi => wfi.NestedXamlDefinition).ToArray(),
null, parameterValidation, modulePath, true, workflowAttributes, null, parameterValidation, modulePath, true, workflowAttributes,
scriptFile, scriptSource, rootWorkflowName); scriptFile, scriptSource, rootWorkflowName, sourceLanguageMode, attributeAstCollection);
var helpContent = func.GetHelpContent(scriptBlockTokenCache); var helpContent = func.GetHelpContent(scriptBlockTokenCache);
if (helpContent != null) if (helpContent != null)
@@ -3545,7 +3545,7 @@ namespace Microsoft.PowerShell.Commands
{ {
if (DownLevelHelper.NativeFilteringSupported()) if (DownLevelHelper.NativeFilteringSupported())
{ {
Collection<string> ekuCollection = System.Management.Automation.SecuritySupport.GetCertEKU(cert); Collection<string> ekuCollection = System.Management.Automation.Internal.SecuritySupport.GetCertEKU(cert);
foreach (string oidString in ekuCollection) foreach (string oidString in ekuCollection)
{ {
@@ -5,6 +5,7 @@ Copyright (c) Microsoft Corporation. All rights reserved.
using System; using System;
using System.Management.Automation; using System.Management.Automation;
using System.Management.Automation.Internal;
using Dbg=System.Management.Automation.Diagnostics; using Dbg=System.Management.Automation.Diagnostics;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections; using System.Collections;
@@ -110,17 +110,15 @@ namespace Microsoft.PowerShell.Commands
protected override void ProcessRecord() protected override void ProcessRecord()
{ {
// In ConstrainedLanguage, XAML workflows are not supported (even from a trusted FullLanguage state), // In ConstrainedLanguage, XAML workflows are not supported (even from a trusted FullLanguage state),
// since we can't prevent tampering. // unless they are signed in-box OS binaries.
bool checkSignatures = false;
if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) || if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ||
(this.SessionState.LanguageMode == PSLanguageMode.ConstrainedLanguage)) (this.SessionState.LanguageMode == PSLanguageMode.ConstrainedLanguage))
{ {
// However, this static internal property can be changed by tests that can already run a script // However, this static internal property can be changed by tests that can already run a script
// in full-language mode. // in full-language mode.
PropertyInfo xamlProperty = typeof(SystemPolicy).GetProperty("XamlWorkflowSupported", BindingFlags.NonPublic | BindingFlags.Static); PropertyInfo xamlProperty = typeof(SystemPolicy).GetProperty("XamlWorkflowSupported", BindingFlags.NonPublic | BindingFlags.Static);
if (! ((bool) xamlProperty.GetValue(null, null))) checkSignatures = !((bool)xamlProperty.GetValue(null, null));
{
throw new NotSupportedException(Resources.XamlWorkflowsNotSupported);
}
} }
string dependentWorkflowAssemblyPath = string.Empty; string dependentWorkflowAssemblyPath = string.Empty;
@@ -174,8 +172,10 @@ namespace Microsoft.PowerShell.Commands
WriteError(er); WriteError(er);
Tracer.TraceErrorRecord(er); Tracer.TraceErrorRecord(er);
continue; continue;
} }
CheckFileSignatureAsNeeded(checkSignatures, resolvedPath);
try try
{ {
// Finally load the file. If there is an access violation, write a // Finally load the file. If there is an access violation, write a
@@ -251,6 +251,8 @@ namespace Microsoft.PowerShell.Commands
continue; continue;
} }
CheckFileSignatureAsNeeded(checkSignatures, resolvedPath);
FunctionDetails detailsToUseForUpdate = null; FunctionDetails detailsToUseForUpdate = null;
try try
{ {
@@ -302,7 +304,8 @@ namespace Microsoft.PowerShell.Commands
requiredAssemblies, requiredAssemblies,
dependentWorkflowContent.ToArray(), dependentWorkflowContent.ToArray(),
dependentWorkflowAssemblyPath, dependentWorkflowAssemblyPath,
resolvedPath); resolvedPath,
this.SessionState.LanguageMode);
// check if the function cache already has the entry to this file // check if the function cache already has the entry to this file
// If detailsToUseForUpdate is not null it is a forced import module // If detailsToUseForUpdate is not null it is a forced import module
@@ -342,6 +345,14 @@ namespace Microsoft.PowerShell.Commands
} //ProcessRecord } //ProcessRecord
private static void CheckFileSignatureAsNeeded(bool checkSignatures, string filePath)
{
if (checkSignatures && !System.Management.Automation.Internal.SecuritySupport.IsProductBinary(filePath))
{
throw new NotSupportedException(Resources.XamlWorkflowsNotSupported);
}
}
private static readonly ConcurrentDictionary<string, FunctionDetails> FunctionCache = private static readonly ConcurrentDictionary<string, FunctionDetails> FunctionCache =
new ConcurrentDictionary<string, FunctionDetails>(StringComparer.OrdinalIgnoreCase); new ConcurrentDictionary<string, FunctionDetails>(StringComparer.OrdinalIgnoreCase);
@@ -358,7 +369,15 @@ namespace Microsoft.PowerShell.Commands
/// <param name="dependentWorkflows">Any workflows required by this workflow.</param> /// <param name="dependentWorkflows">Any workflows required by this workflow.</param>
/// <param name="dependentAssemblyPath">Path to the dependent assembly.</param> /// <param name="dependentAssemblyPath">Path to the dependent assembly.</param>
/// <param name="resolvedPath">Resolved Path of the xaml</param> /// <param name="resolvedPath">Resolved Path of the xaml</param>
private static FunctionDetails GenerateFunctionFromXaml(string name, string xaml, Dictionary<string, string> requiredAssemblies, string[] dependentWorkflows, string dependentAssemblyPath, string resolvedPath) /// <param name="sourceLanguageMode">Language mode of source in which workflow should run</param>
private static FunctionDetails GenerateFunctionFromXaml(
string name,
string xaml,
Dictionary<string, string> requiredAssemblies,
string[] dependentWorkflows,
string dependentAssemblyPath,
string resolvedPath,
PSLanguageMode sourceLanguageMode)
{ {
if (name == null) if (name == null)
{ {
@@ -368,7 +387,21 @@ namespace Microsoft.PowerShell.Commands
} }
string modulePath = System.IO.Path.GetDirectoryName(resolvedPath); string modulePath = System.IO.Path.GetDirectoryName(resolvedPath);
string functionDefinition = CreateFunctionFromXaml(name, xaml, requiredAssemblies, dependentWorkflows, dependentAssemblyPath, null, modulePath, false, "[CmdletBinding()]"); string functionDefinition = CreateFunctionFromXaml(
name,
xaml,
requiredAssemblies,
dependentWorkflows,
dependentAssemblyPath,
null,
modulePath,
false,
"[CmdletBinding()]",
null, /* scriptContent */
null, /* fullScript */
null, /* rootWorkflowName */
sourceLanguageMode,
null);
FunctionDetails details = new FunctionDetails FunctionDetails details = new FunctionDetails
{Name = name, FunctionDefinition = functionDefinition, Xaml = xaml}; {Name = name, FunctionDefinition = functionDefinition, Xaml = xaml};
@@ -438,6 +471,24 @@ namespace Microsoft.PowerShell.Commands
[SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "workFlowDefinition")] [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "workFlowDefinition")]
public static ContainerParentJob StartWorkflowApplication(PSCmdlet command, string jobName, string workflowGuid, bool startAsync, public static ContainerParentJob StartWorkflowApplication(PSCmdlet command, string jobName, string workflowGuid, bool startAsync,
bool parameterCollectionProcessed, Hashtable[] parameters, bool debuggerActive) bool parameterCollectionProcessed, Hashtable[] parameters, bool debuggerActive)
{
return StartWorkflowApplication(command, jobName, workflowGuid, startAsync, parameterCollectionProcessed, parameters, false, null);
}
/// <summary>
/// Executes an instance of the workflow object graph identified by the passed
/// GUID, binding parameters from the Parameters hastable.
/// </summary>
/// <param name="command">The powershell command.</param>
/// <param name="workflowGuid">The GUID used to identify the workflow to run.</param>
/// <param name="parameters">The parameters to pass to the workflow instance.</param>
/// <param name="jobName">The friendly name for the job</param>
/// <param name="parameterCollectionProcessed">True if there was a PSParameters collection</param>
/// <param name="startAsync"></param>
/// <param name="debuggerActive">True if debugger is in active state.</param>
/// <param name="SourceLanguageMode">Language mode of source creating workflow.</param>
public static ContainerParentJob StartWorkflowApplication(PSCmdlet command, string jobName, string workflowGuid, bool startAsync,
bool parameterCollectionProcessed, Hashtable[] parameters, bool debuggerActive, string SourceLanguageMode)
{ {
Guid trackingGuid = Guid.NewGuid(); Guid trackingGuid = Guid.NewGuid();
_structuredTracer.BeginStartWorkflowApplication(trackingGuid); _structuredTracer.BeginStartWorkflowApplication(trackingGuid);
@@ -573,6 +624,15 @@ namespace Microsoft.PowerShell.Commands
myJob = command.JobManager.NewJob(specification) as ContainerParentJob; myJob = command.JobManager.NewJob(specification) as ContainerParentJob;
_structuredTracer.EndCreateNewJob(trackingGuid); _structuredTracer.EndCreateNewJob(trackingGuid);
// Pass the source language mode to the workflow job so that it can be
// applied during activity execution.
PSLanguageMode sourceLanguageModeValue;
PSLanguageMode? sourceLanguageMode = null;
if (!string.IsNullOrEmpty(SourceLanguageMode) && Enum.TryParse<PSLanguageMode>(SourceLanguageMode, out sourceLanguageModeValue))
{
sourceLanguageMode = sourceLanguageModeValue;
}
// Raise engine event of new WF job start for debugger, if // Raise engine event of new WF job start for debugger, if
// debugger is active (i.e., has breakpoints set). // debugger is active (i.e., has breakpoints set).
if (debuggerActive) if (debuggerActive)
@@ -581,13 +641,14 @@ namespace Microsoft.PowerShell.Commands
} }
if (startAsync) if (startAsync)
{
if (!PSSessionConfigurationData.IsServerManager)
{ {
foreach(PSWorkflowJob childjob in myJob.ChildJobs) foreach(PSWorkflowJob childjob in myJob.ChildJobs)
{
if (!PSSessionConfigurationData.IsServerManager)
{ {
childjob.EnableStreamUnloadOnPersistentState(); childjob.EnableStreamUnloadOnPersistentState();
} }
childjob.SourceLanguageMode = sourceLanguageMode;
} }
myJob.StartJobAsync(); myJob.StartJobAsync();
} }
@@ -598,6 +659,7 @@ namespace Microsoft.PowerShell.Commands
foreach (PSWorkflowJob childJob in myJob.ChildJobs) foreach (PSWorkflowJob childJob in myJob.ChildJobs)
{ {
childJob.SynchronousExecution = true; childJob.SynchronousExecution = true;
childJob.SourceLanguageMode = sourceLanguageMode;
} }
myJob.StartJob(); myJob.StartJob();
} }
@@ -605,8 +667,8 @@ namespace Microsoft.PowerShell.Commands
// write an event specifying that job creation is done // write an event specifying that job creation is done
_structuredTracer.EndStartWorkflowApplication(trackingGuid); _structuredTracer.EndStartWorkflowApplication(trackingGuid);
_structuredTracer.TrackingGuidContainerParentJobCorrelation(trackingGuid, myJob.InstanceId); _structuredTracer.TrackingGuidContainerParentJobCorrelation(trackingGuid, myJob.InstanceId);
return myJob;
return myJob;
} }
private static void RaiseWFJobEvent(PSCmdlet command, ContainerParentJob job, bool startAsync) private static void RaiseWFJobEvent(PSCmdlet command, ContainerParentJob job, bool startAsync)
@@ -1010,6 +1072,46 @@ namespace Microsoft.PowerShell.Commands
string fullScript, string fullScript,
string rootWorkflowName string rootWorkflowName
) )
{
return CreateFunctionFromXaml(name, xaml, requiredAssemblies, dependentWorkflows, dependentAssemblyPath, parameterValidation, modulePath,
scriptWorkflow, workflowAttributes, scriptContent, fullScript, rootWorkflowName, null, null);
}
/// <summary>
/// Creates a workflow activity based on the provided Xaml and returns PowerShell script that will
/// run the workflow.
/// </summary>
/// <param name="name">Workflow name</param>
/// <param name="xaml">Workflow Xaml definition</param>
/// <param name="requiredAssemblies">Required assemblies</param>
/// <param name="dependentWorkflows">Dependent workflows</param>
/// <param name="dependentAssemblyPath">Path for dependent assemblies</param>
/// <param name="parameterValidation">Workflow parameters</param>
/// <param name="modulePath">Module path</param>
/// <param name="scriptWorkflow">True if this is script workflow</param>
/// <param name="workflowAttributes">the attribute string to use for the workflow, should be '[CmdletBinding()]'</param>
/// <param name="scriptContent">File path containing script content.</param>
/// <param name="fullScript">Full source script.</param>
/// <param name="rootWorkflowName">Only root Workflow will be compiled</param>
/// <param name="sourceLanguageMode">Language mode of source that is creating the workflow</param>
/// <param name="attributeAstCollection">Optional collection of parameter attribute Asts</param>
/// <returns></returns>
public static string CreateFunctionFromXaml(
string name,
string xaml,
Dictionary<string, string> requiredAssemblies,
string[] dependentWorkflows,
string dependentAssemblyPath,
Dictionary<string, ParameterAst> parameterValidation,
string modulePath,
bool scriptWorkflow,
string workflowAttributes,
string scriptContent,
string fullScript,
string rootWorkflowName,
PSLanguageMode? sourceLanguageMode,
ReadOnlyCollection<AttributeAst> attributeAstCollection
)
{ {
// check to see if the specified name is allowed // check to see if the specified name is allowed
if (!Regex.IsMatch(name, functionNamePattern)) if (!Regex.IsMatch(name, functionNamePattern))
@@ -1304,9 +1406,15 @@ namespace Microsoft.PowerShell.Commands
completeFunctionDefinitionTemplate.AppendLine(" }}"); completeFunctionDefinitionTemplate.AppendLine(" }}");
completeFunctionDefinitionTemplate.AppendLine(FunctionBodyTemplate); completeFunctionDefinitionTemplate.AppendLine(FunctionBodyTemplate);
// Mark the function definition with sourceLanguageMode (language mode that function can run under, i.e.,
// as trusted or not trusted), unless the workflow script is marked with the "SecurityCritical" attribute in
// which case the function will always be run under the current system lock down setting.
bool isSecurityCritical = ContainsSecurityCriticalAttribute(attributeAstCollection);
string sourceLanguageModeStr = (!isSecurityCritical && (sourceLanguageMode != null)) ? sourceLanguageMode.ToString() : string.Empty;
// Combine the pieces to create the complete function // Combine the pieces to create the complete function
string functionDefinition = String.Format(CultureInfo.InvariantCulture, completeFunctionDefinitionTemplate.ToString(), string functionDefinition = String.Format(CultureInfo.InvariantCulture, completeFunctionDefinitionTemplate.ToString(),
defaultUpdates, workflowGuid, modulePath); defaultUpdates, workflowGuid, modulePath, sourceLanguageModeStr);
#if DEBUG #if DEBUG
// Verify that the generated function is valid powershell. This is only an issue when changing the // Verify that the generated function is valid powershell. This is only an issue when changing the
@@ -1334,6 +1442,23 @@ namespace Microsoft.PowerShell.Commands
return functionDefinition; return functionDefinition;
} }
private static Type securityCriticalAttributeType = typeof(System.Security.SecurityCriticalAttribute);
private static bool ContainsSecurityCriticalAttribute(ReadOnlyCollection<AttributeAst> attributeAsts)
{
if (attributeAsts != null)
{
foreach (var attributeAst in attributeAsts)
{
if (attributeAst.TypeName.GetReflectionAttributeType() == securityCriticalAttributeType)
{
return true;
}
}
}
return false;
}
/// <summary> /// <summary>
/// ///
@@ -1515,6 +1640,9 @@ namespace Microsoft.PowerShell.Commands
# which uses this as a base path to find localized content files. # which uses this as a base path to find localized content files.
$psBoundParameters['" + Constants.ModulePath + @"'] = '{2}' $psBoundParameters['" + Constants.ModulePath + @"'] = '{2}'
# Variable that contains the source language mode.
[string] $SourceLanguageMode = '{3}'
if (Test-Path variable:\PSSenderInfo) if (Test-Path variable:\PSSenderInfo)
{{ {{
$psBoundParameters['" + Constants.PSSenderInfo + @"'] = $PSSenderInfo $psBoundParameters['" + Constants.PSSenderInfo + @"'] = $PSSenderInfo
@@ -1693,7 +1821,8 @@ namespace Microsoft.PowerShell.Commands
$AsJob, $AsJob,
$parameterCollectionProcessed, $parameterCollectionProcessed,
$finalParameterCollection, $finalParameterCollection,
$debuggerActive) $debuggerActive,
$SourceLanguageMode)
}} }}
catch catch
{{ {{
@@ -80,23 +80,29 @@ namespace Microsoft.PowerShell.Workflow
return EndGetRunspace(asyncResult); return EndGetRunspace(asyncResult);
} }
private Runspace AssignRunspaceIfPossible() private Runspace AssignRunspaceIfPossible(PSLanguageMode? sourceLanguageMode = null)
{ {
Runspace runspace = null; Runspace runspace = null;
PSLanguageMode languageMode = (sourceLanguageMode != null) ? sourceLanguageMode.Value :
(_languageMode != null) ? _languageMode.Value : GetSystemLanguageMode();
lock (_runspaceCache.TimerServicingSyncObject) lock (_runspaceCache.TimerServicingSyncObject)
{ {
// Retrieve or create a local runspace having the same language mode as the source, if provided.
foreach (Item<Runspace> item in _runspaceCache.Cast<Item<Runspace>>().Where(item => !item.Busy)) foreach (Item<Runspace> item in _runspaceCache.Cast<Item<Runspace>>().Where(item => !item.Busy))
{
if (item.Value.SessionStateProxy.LanguageMode == languageMode)
{ {
item.Idle = false; item.Idle = false;
item.Busy = true; item.Busy = true;
runspace = item.Value; runspace = item.Value;
break; break;
} }
}
if ((runspace == null || runspace.RunspaceStateInfo.State != RunspaceState.Opened) && if ((runspace == null || runspace.RunspaceStateInfo.State != RunspaceState.Opened) &&
(_maxRunspaces == MaxRunspacesPossible || _runspaceCache.Cache.Count < _maxRunspaces)) (_maxRunspaces == MaxRunspacesPossible || _runspaceCache.Cache.Count < _maxRunspaces))
{ {
runspace = CreateLocalActivityRunspace(_languageMode); runspace = CreateLocalActivityRunspace(languageMode);
runspace.Open(); runspace.Open();
_tracer.WriteMessage("New local runspace created"); _tracer.WriteMessage("New local runspace created");
@@ -108,6 +114,11 @@ namespace Microsoft.PowerShell.Workflow
return runspace; return runspace;
} }
private static PSLanguageMode GetSystemLanguageMode()
{
return (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ?
PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage;
}
private void TraceThreadPoolInfo(string message) private void TraceThreadPoolInfo(string message)
{ {
@@ -208,7 +219,23 @@ namespace Microsoft.PowerShell.Workflow
LocalRunspaceAsyncResult asyncResult = new LocalRunspaceAsyncResult(state, callback, Guid.Empty); LocalRunspaceAsyncResult asyncResult = new LocalRunspaceAsyncResult(state, callback, Guid.Empty);
Runspace runspace = AssignRunspaceIfPossible(); // Get the source language mode from the activity arguments if available and pass to runspace fetching.
PSLanguageMode? sourceLanguageMode = null;
RunCommandsArguments args = state as RunCommandsArguments;
if (args != null)
{
PSWorkflowRuntime wfRuntime = args.WorkflowHost as PSWorkflowRuntime;
if (wfRuntime != null)
{
PSWorkflowJob wfJob = wfRuntime.JobManager.GetJob(args.PSActivityContext.JobInstanceId);
if (wfJob != null)
{
sourceLanguageMode = wfJob.SourceLanguageMode;
}
}
}
Runspace runspace = AssignRunspaceIfPossible(sourceLanguageMode);
if (runspace != null) if (runspace != null)
{ {
asyncResult.Runspace = runspace; asyncResult.Runspace = runspace;
@@ -1205,6 +1205,8 @@ namespace Microsoft.PowerShell.Workflow
internal bool? IsSuspendable = null; internal bool? IsSuspendable = null;
internal PSLanguageMode? SourceLanguageMode { get; set; }
#endregion Internal Accessors #endregion Internal Accessors
#region Internal Methods #region Internal Methods
@@ -413,7 +413,7 @@ For more information about how to add checkpoints, see the help topics for Windo
<comment>{0} is a number and should not be localized.</comment> <comment>{0} is a number and should not be localized.</comment>
</data> </data>
<data name="XamlWorkflowsNotSupported" xml:space="preserve"> <data name="XamlWorkflowsNotSupported" xml:space="preserve">
<value>Cannot load workflow. XAML-based workflows are not supported in this language mode. Only script-based workflows are supported.</value> <value>Cannot load the workflow. Only signed in-box XAML-based workflows or script-based workflows are supported in the current language mode.</value>
</data> </data>
<data name="RetryingConnection" xml:space="preserve"> <data name="RetryingConnection" xml:space="preserve">
<value>Retrying activity connection: attempt {0} of {1}.</value> <value>Retrying activity connection: attempt {0} of {1}.</value>
@@ -1,17 +1,17 @@
@{ @{
GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59" GUID="1DA87E53-152B-403E-98DC-74D7B4D63D59"
Author="Microsoft Corporation" Author="Microsoft Corporation"
CompanyName="Microsoft Corporation" CompanyName="Microsoft Corporation"
Copyright="© Microsoft Corporation. All rights reserved." Copyright="© Microsoft Corporation. All rights reserved."
ModuleVersion="3.1.0.0" ModuleVersion="3.1.0.0"
PowerShellVersion="3.0" PowerShellVersion="3.0"
CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide", CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide",
"Out-File", "Out-String", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json", "Out-File", "Out-String", "Get-FormatData", "Export-FormatData", "ConvertFrom-Json", "ConvertTo-Json",
"Invoke-RestMethod", "Invoke-WebRequest", "Invoke-RestMethod", "Invoke-WebRequest", "Register-ObjectEvent", "Register-EngineEvent",
"Register-ObjectEvent", "Register-EngineEvent", "Wait-Event", "Get-Event", "Remove-Event", "Wait-Event", "Get-Event", "Remove-Event", "Get-EventSubscriber", "Unregister-Event",
"Get-EventSubscriber", "Unregister-Event", "New-Event", "Add-Member", "Add-Type", "Compare-Object", "New-Event", "Add-Member", "Add-Type", "Compare-Object", "ConvertFrom-StringData",
"ConvertFrom-StringData", "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Export-Csv", "Import-Csv", "ConvertTo-Csv", "ConvertFrom-Csv", "Export-Alias", "Invoke-Expression",
"Invoke-Expression", "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random", "Get-Alias", "Get-Culture", "Get-Date", "Get-Host", "Get-Member", "Get-Random",
"Get-UICulture", "Get-Unique", "Import-Alias", "Import-LocalizedData", "Get-UICulture", "Get-Unique", "Import-Alias", "Import-LocalizedData",
"Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date", "Select-String", "Measure-Object", "New-Alias", "New-TimeSpan", "Read-Host", "Set-Alias", "Set-Date",
"Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData", "Start-Sleep", "Tee-Object", "Measure-Command", "Update-TypeData", "Update-FormatData",
@@ -20,7 +20,6 @@ CmdletsToExport= "Format-List", "Format-Custom", "Format-Table", "Format-Wide",
"Clear-Variable", "Export-Clixml", "Import-Clixml", "ConvertTo-Xml", "Select-Xml", "Write-Debug", "Clear-Variable", "Export-Clixml", "Import-Clixml", "ConvertTo-Xml", "Select-Xml", "Write-Debug",
"Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint", "Write-Verbose", "Write-Warning", "Write-Error", "Write-Information", "Write-Output", "Set-PSBreakpoint",
"Get-PSBreakpoint", "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack", "Get-PSBreakpoint", "Remove-PSBreakpoint", "Enable-PSBreakpoint", "Disable-PSBreakpoint", "Get-PSCallStack",
"Get-TraceSource", "Set-TraceSource", "Trace-Command",
"Unblock-File", "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug", "Unblock-File", "Get-Runspace", "Debug-Runspace", "Enable-RunspaceDebug", "Disable-RunspaceDebug",
"Get-RunspaceDebug", "Wait-Debugger" "Get-RunspaceDebug", "Wait-Debugger"
FunctionsToExport= "Get-FileHash", "New-TemporaryFile", "New-Guid", "Format-Hex", "Import-PowerShellDataFile", FunctionsToExport= "Get-FileHash", "New-TemporaryFile", "New-Guid", "Format-Hex", "Import-PowerShellDataFile",
@@ -1,8 +1,9 @@
workflow Invoke-AsWorkflow workflow Invoke-AsWorkflow
{ {
<# <#
.EXTERNALHELP Microsoft.PowerShell.Workflow.ServiceCore.dll-help.xml .EXTERNALHELP Microsoft.PowerShell.Workflow.ServiceCore.dll-help.xml
#> #>
[System.Security.SecurityCritical()]
[CmdletBinding(DefaultParameterSetName='Command', HelpUri='http://go.microsoft.com/fwlink/?LinkId=238267')] [CmdletBinding(DefaultParameterSetName='Command', HelpUri='http://go.microsoft.com/fwlink/?LinkId=238267')]
param( param(
[Parameter(Mandatory=$true,ParameterSetName="Command")] [Parameter(Mandatory=$true,ParameterSetName="Command")]
@@ -3777,23 +3777,38 @@ namespace Microsoft.PowerShell.Commands
if (workflowsToProcess != null && workflowsToProcess.Count > 0) if (workflowsToProcess != null && workflowsToProcess.Count > 0)
{ {
// In ConstrainedLanguage, XAML workflows are not supported (even from a trusted FullLanguage state), // In ConstrainedLanguage, XAML workflows are not supported (even from a trusted FullLanguage state),
// since we can't prevent tampering. // unless they are signed in-box OS binaries.
if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) || if ((SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ||
(Context.LanguageMode == PSLanguageMode.ConstrainedLanguage)) (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage))
{ {
// However, this static internal property can be changed by tests that can already run a script // However, this static internal property can be changed by tests that can already run a script
// in full-language mode. // in full-language mode.
if (! SystemPolicy.XamlWorkflowSupported) if (! SystemPolicy.XamlWorkflowSupported)
{
foreach (string workflowPath in ResolveWorkflowFiles(moduleBase, workflowsToProcess))
{
if (!SecuritySupport.IsProductBinary(workflowPath))
{ {
throw new NotSupportedException(Modules.XamlWorkflowsNotSupported); throw new NotSupportedException(Modules.XamlWorkflowsNotSupported);
} }
} }
}
}
SessionStateInternal oldSessionStateWF = Context.EngineSessionState; SessionStateInternal oldSessionStateWF = Context.EngineSessionState;
PSLanguageMode? savedLanguageMode = null;
try try
{ {
Context.EngineSessionState = ss.Internal; Context.EngineSessionState = ss.Internal;
// Always run workflow import script as trusted since only signed in-box files can be imported
// on locked down machines.
if (Context.LanguageMode != PSLanguageMode.FullLanguage)
{
savedLanguageMode = Context.LanguageMode;
Context.LanguageMode = PSLanguageMode.FullLanguage;
}
if (dependentWorkflows != null && dependentWorkflows.Count > 0) if (dependentWorkflows != null && dependentWorkflows.Count > 0)
{ {
ScriptBlock importWorkflow = ScriptBlock.Create(Context, ScriptBlock importWorkflow = ScriptBlock.Create(Context,
@@ -3811,7 +3826,7 @@ namespace Microsoft.PowerShell.Commands
else else
{ {
ScriptBlock importWorkflow = ScriptBlock.Create(Context, ScriptBlock importWorkflow = ScriptBlock.Create(Context,
"param($files, $dependentFiles) Microsoft.PowerShell.Workflow.ServiceCore\\Import-PSWorkflow -Path \"$files\" -Force:$"+BaseForce "param($files, $dependentFiles) Microsoft.PowerShell.Workflow.ServiceCore\\Import-PSWorkflow -Path \"$files\" -Force:$" + BaseForce
); );
foreach (string workflowPath in ResolveWorkflowFiles(moduleBase, workflowsToProcess)) foreach (string workflowPath in ResolveWorkflowFiles(moduleBase, workflowsToProcess))
@@ -3830,6 +3845,11 @@ namespace Microsoft.PowerShell.Commands
finally finally
{ {
Context.EngineSessionState = oldSessionStateWF; Context.EngineSessionState = oldSessionStateWF;
if (savedLanguageMode != null)
{
Context.LanguageMode = savedLanguageMode.Value;
}
} }
} }
} }
@@ -11084,6 +11084,9 @@ namespace System.Management.Automation.Internal
/// <summary/> /// <summary/>
List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, out ParseException parsingErrors); List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, out ParseException parsingErrors);
/// <summary/>
List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, InitialSessionState initialSessionState, PSLanguageMode? languageMode, out ParseException parsingErrors);
/// <summary/> /// <summary/>
List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, string rootWorkflowName); List<WorkflowInfo> CompileWorkflows(ScriptBlockAst ast, PSModuleInfo definingModule, string rootWorkflowName);
@@ -1828,7 +1828,7 @@ namespace System.Management.Automation
if (_scriptBlock.HasBeginBlock) if (_scriptBlock.HasBeginBlock)
{ {
RunClause(_runOptimized ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock, AutomationNull.Value, _input.GetEnumerator()); RunClause(_runOptimized ? _scriptBlock.BeginBlock : _scriptBlock.UnoptimizedBeginBlock, AutomationNull.Value, _input);
} }
} }
@@ -1851,7 +1851,7 @@ namespace System.Management.Automation
} }
if (_scriptBlock.HasProcessBlock) if (_scriptBlock.HasProcessBlock)
{ {
RunClause(_runOptimized ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock, dollarUnder, _input.GetEnumerator()); RunClause(_runOptimized ? _scriptBlock.ProcessBlock : _scriptBlock.UnoptimizedProcessBlock, dollarUnder, _input);
_input.Clear(); _input.Clear();
} }
} }
@@ -1865,7 +1865,7 @@ namespace System.Management.Automation
if (_scriptBlock.HasEndBlock) if (_scriptBlock.HasEndBlock)
{ {
RunClause(_runOptimized ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock, AutomationNull.Value, _input.ToArray().GetEnumerator()); RunClause(_runOptimized ? _scriptBlock.EndBlock : _scriptBlock.UnoptimizedEndBlock, AutomationNull.Value, _input.ToArray());
} }
} }
@@ -1125,7 +1125,8 @@ namespace System.Management.Automation
try try
{ {
var converterInstance = Utils.GetAstToWorkflowConverterAndEnsureWorkflowModuleLoaded(context); var converterInstance = Utils.GetAstToWorkflowConverterAndEnsureWorkflowModuleLoaded(context);
var workflows = converterInstance.CompileWorkflows(scriptBlockAst, context.EngineSessionState.Module, null, out parseErrors); PSLanguageMode? languageMode = (context != null) ? context.LanguageMode : (PSLanguageMode?) null;
var workflows = converterInstance.CompileWorkflows(scriptBlockAst, context.EngineSessionState.Module, null, languageMode, out parseErrors);
foreach (var workflow in workflows) foreach (var workflow in workflows)
{ {
context.EngineSessionState.SetWorkflowRaw(workflow, context.EngineSessionState.SetWorkflowRaw(workflow,
@@ -597,7 +597,7 @@
<value>Cannot define the workflow. The language mode for this session is incompatible with the system-wide language mode.</value> <value>Cannot define the workflow. The language mode for this session is incompatible with the system-wide language mode.</value>
</data> </data>
<data name="XamlWorkflowsNotSupported" xml:space="preserve"> <data name="XamlWorkflowsNotSupported" xml:space="preserve">
<value>Cannot load the workflow. XAML-based workflows are not supported in the current language mode. Only script-based workflows are supported in this language mode.</value> <value>Cannot load the workflow. Only signed in-box XAML-based workflows or script-based workflows are supported in the current language mode.</value>
</data> </data>
<data name="CannotDetectNetFrameworkVersion" xml:space="preserve"> <data name="CannotDetectNetFrameworkVersion" xml:space="preserve">
<value>Cannot verify the Microsoft .NET Framework version {0} because it is not included in the list of permitted versions.</value> <value>Cannot verify the Microsoft .NET Framework version {0} because it is not included in the list of permitted versions.</value>
@@ -100,7 +100,7 @@ namespace Microsoft.PowerShell
} }
} }
namespace System.Management.Automation namespace System.Management.Automation.Internal
{ {
/// <summary> /// <summary>
/// The SAFER policy associated with this file /// The SAFER policy associated with this file
@@ -118,7 +118,10 @@ namespace System.Management.Automation
Disallowed = 2 Disallowed = 2
} }
internal static class SecuritySupport /// <summary>
/// Security Support APIs
/// </summary>
public static class SecuritySupport
{ {
#region execution policy #region execution policy
@@ -414,7 +417,12 @@ namespace System.Management.Automation
} }
} }
internal static bool IsProductBinary(string file) /// <summary>
/// Returns true if file has product binary signature
/// </summary>
/// <param name="file">Name of file to check</param>
/// <returns>True when file has product binary signature</returns>
public static bool IsProductBinary(string file)
{ {
if(String.IsNullOrEmpty(file) || (! IO.File.Exists(file))) if(String.IsNullOrEmpty(file) || (! IO.File.Exists(file)))
{ {