diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index d589cd887..814614f93 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -63,6 +63,15 @@ namespace Microsoft.PowerShell } } + internal bool ShowBanner + { + get + { + Dbg.Assert(dirty, "Parse has not been called yet"); + return showBanner; + } + } + internal bool NoExit { get @@ -229,7 +238,7 @@ namespace Microsoft.PowerShell ui.WriteLine(""); } - private void ShowBanner() + private void DisplayBanner() { // We need to show the banner only if it has not been already shown in native layer if (!showInitialPrompt) @@ -699,7 +708,7 @@ namespace Microsoft.PowerShell if (showBanner && !showHelp) { - ShowBanner(); + DisplayBanner(); } Dbg.Assert( diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 44e580197..5eeef3c10 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -10,6 +10,7 @@ using System.Text; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; @@ -36,7 +37,7 @@ using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; using NakedWin32Handle = System.IntPtr; using System.Management.Automation.Tracing; using Microsoft.PowerShell.Telemetry.Internal; - +using Debugger = System.Management.Automation.Debugger; namespace Microsoft.PowerShell { @@ -51,7 +52,8 @@ namespace Microsoft.PowerShell : PSHost, IDisposable, - IHostSupportsInteractiveSession + IHostSupportsInteractiveSession, + IHostProvidesTelemetryData { #region static methods @@ -202,17 +204,8 @@ namespace Microsoft.PowerShell theConsoleHost.UI.WriteWarningLine(preStartWarning); } - using (theConsoleHost) + try { -#region Telemetry - - // Data Point to Collect: PowerShell Version on this device - // If null Version is supplied, the Telemetry infrastucture will log an empty value for null - string version = theConsoleHost.ver.ToString(); - Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceMessage("PSCONSOLEHOST_START", new { PSVersion = version }); - -#endregion - cpp = new CommandLineParameterParser(theConsoleHost, theConsoleHost.ver, bannerText, helpText); string[] tempArgs = new string[args.GetLength(0)]; args.CopyTo(tempArgs, 0); @@ -239,15 +232,17 @@ namespace Microsoft.PowerShell else if (cpp.NamedPipeServerMode) { ClrFacade.StartProfileOptimization("StartupProfileData-NamedPipeServerMode"); - System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode(cpp.ConfigurationName); + System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode( + cpp.ConfigurationName); exitCode = 0; } else if (cpp.SocketServerMode) { ClrFacade.StartProfileOptimization("StartupProfileData-SocketServerMode"); - System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(cpp.InitialCommand, cpp.ConfigurationName); + System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(cpp.InitialCommand, + cpp.ConfigurationName); exitCode = 0; - } + } else { ClrFacade.StartProfileOptimization( @@ -256,7 +251,14 @@ namespace Microsoft.PowerShell : "StartupProfileData-NonInteractive"); exitCode = theConsoleHost.Run(cpp, !string.IsNullOrEmpty(preStartWarning)); } + } + finally + { + TelemetryAPI.ReportExitTelemetry(theConsoleHost); + theConsoleHost.Dispose(); + } + unchecked { return (int)exitCode; @@ -970,6 +972,22 @@ namespace Microsoft.PowerShell } } + bool IHostProvidesTelemetryData.HostIsInteractive + { + get + { + return !cpp.NonInteractive && !cpp.AbortStartup && + ((cpp.InitialCommand == null && cpp.File == null) || cpp.NoExit); + } + } + double IHostProvidesTelemetryData.ProfileLoadTimeInMS { get { return _profileLoadTimeInMS; } } + double IHostProvidesTelemetryData.ReadyForInputTimeInMS { get { return _readyForInputTimeInMS; } } + int IHostProvidesTelemetryData.InteractiveCommandCount { get { return _interactiveCommandCount; } } + + private double _profileLoadTimeInMS; + private double _readyForInputTimeInMS; + private int _interactiveCommandCount; + #endregion overrides #region non-overrides @@ -1370,6 +1388,7 @@ namespace Microsoft.PowerShell RunspaceCreationEventArgs args = new RunspaceCreationEventArgs(initialCommand, skipProfiles, staMode, importSystemModules, configurationName, initialCommandArgs); CreateRunspace(args); + if (ExitCode == ExitCodeInitFailure) { break; } if (!noExit && !ui.ReadFromStdin) @@ -1564,6 +1583,9 @@ namespace Microsoft.PowerShell PSTask.PowershellConsoleStartup, PSKeyword.UseAlwaysOperational); } + // Record how long it took from process start to runspace open for telemetry. + _readyForInputTimeInMS = (DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds; + DoRunspaceInitialization(importSystemModules, skipProfiles, initialCommand, configurationName, initialCommandArgs); } @@ -1697,10 +1719,21 @@ namespace Microsoft.PowerShell // 3. host independent profile of the current user // 4. host specific profile of the current user + var sw = new Stopwatch(); + sw.Start(); RunProfile(allUsersProfile, exec); RunProfile(allUsersHostSpecificProfile, exec); RunProfile(currentUserProfile, exec); RunProfile(currentUserHostSpecificProfile, exec); + sw.Stop(); + + var profileLoadTimeInMs = sw.ElapsedMilliseconds; + if (profileLoadTimeInMs > 500 && cpp.ShowBanner) + { + Console.Error.WriteLine(ConsoleHostStrings.SlowProfileLoadingMessage, profileLoadTimeInMs); + } + + _profileLoadTimeInMS = profileLoadTimeInMs; } else { @@ -1708,6 +1741,10 @@ namespace Microsoft.PowerShell } } + // Startup is reported after possibly running the profile, but before running the initial command (or file) + // if one is specified. + TelemetryAPI.ReportStartupTelemetry(this); + // If a file was specified as the argument to run, then run it... if (cpp != null && cpp.File != null) { @@ -2714,6 +2751,9 @@ namespace Microsoft.PowerShell parent.PopRunspace(); } } + + if (!inBlockMode) + theConsoleHost._interactiveCommandCount += 1; } } // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx index 6a7c355d8..7c1e06807 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx @@ -133,13 +133,13 @@ PowerShell[.exe] [-PSConsoleFile <file> | -Version <version>] [-NoLogo] [-NoExit] [-NoProfile] [-NonInteractive] [-STA] [-OutputFormat {Text | XML}] [-InputFormat {Text | XML}] + [-ConfigurationName <string>] + [-File fileName [arguments...]] [-ExecutionPolicy <ExecutionPolicy>] [-Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] } ] - [-File fileName [arguments...]] [-ExecutionPolicy <ExecutionPolicy>] PowerShell[.exe] -Help | -? | /? - -PSConsoleFile Loads the specified Windows PowerShell console file. To create a console file, use Export-Console in Windows PowerShell. @@ -170,6 +170,12 @@ PowerShell[.exe] -Help | -? | /? Describes the format of data sent to Windows PowerShell. Valid values are "Text" (text strings) or "XML" (serialized CLIXML format). +-ConfigurationName + Specifies a configuration endpoint in which Windows PowerShell is run. + This can be any endpoint registered on the local machine including the + default Windows PowerShell remoting endpoints or a custom endpoint having + specific user role capabilities. + -Command Executes the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt, and then exits, unless @@ -201,6 +207,7 @@ PowerShell[.exe] -Help | -? | /? EXAMPLES PowerShell -PSConsoleFile SqlSnapin.Psc1 PowerShell -version 1.0 -NoLogo -InputFormat text -OutputFormat XML + PowerShell -ConfigurationName AdminRoles PowerShell -Command {Get-EventLog -LogName security} PowerShell -Command "& {Get-EventLog -LogName security}" diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx index 256ab2726..c4ce91a47 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx @@ -185,4 +185,7 @@ The current session does not support debugging; execution will continue. More than one server mode parameter was specified. Server mode parameters must be used exclusively. - + + Loading personal and system profiles took {0}ms. + + \ No newline at end of file diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index 95bdb87d3..587047c25 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -124,8 +124,9 @@ Copyright (C) 2016 Microsoft Corporation. All rights reserved. PowerShell[.exe] [-PSConsoleFile <file> | -Version <version>] [-NoLogo] [-NoExit] [-Sta] [-Mta] [-NoProfile] [-NonInteractive] - [-InputFormat {Text | XML}] [-OutputFormat {Text | XML}] - [-WindowStyle <style>] [-EncodedCommand <Base64EncodedCommand>] + [-InputFormat {Text | XML}] [-OutputFormat {Text | XML}] + [-WindowStyle <style>] [-EncodedCommand <Base64EncodedCommand>] + [-ConfigurationName <string>] [-File <filePath> <args>] [-ExecutionPolicy <ExecutionPolicy>] [-Command { - | <script-block> [-args <arg-array>] | <string> [<CommandParameters>] } ] @@ -174,6 +175,12 @@ PowerShell[.exe] -Help | -? | /? Accepts a base-64-encoded string version of a command. Use this parameter to submit commands to Windows PowerShell that require complex quotation marks or curly braces. + +-ConfigurationName + Specifies a configuration endpoint in which Windows PowerShell is run. + This can be any endpoint registered on the local machine including the + default Windows PowerShell remoting endpoints or a custom endpoint having + specific user role capabilities. -File Runs the specified script in the local scope ("dot-sourced"), so that the @@ -220,6 +227,7 @@ PowerShell[.exe] -Help | -? | /? EXAMPLES PowerShell -PSConsoleFile SqlSnapIn.Psc1 PowerShell -version 2.0 -NoLogo -InputFormat text -OutputFormat XML + PowerShell -ConfigurationName AdminRoles PowerShell -Command {Get-EventLog -LogName security} PowerShell -Command "& {Get-EventLog -LogName security}" diff --git a/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 index 859ea5512..776ae8c49 100644 --- a/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 +++ b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psd1 @@ -3,8 +3,9 @@ GUID="eb74e8da-9ae2-482a-a648-e96550fb8733" Author="Microsoft Corporation" CompanyName="Microsoft Corporation" Copyright="© Microsoft Corporation. All rights reserved." -ModuleVersion="1.0.0.0" +ModuleVersion="1.0.1.0" FunctionsToExport = @('Compress-Archive', 'Expand-Archive') +DotNetFrameworkVersion = 4.5 CmdletsToExport = @() AliasesToExport = @() NestedModules="Microsoft.PowerShell.Archive.psm1" diff --git a/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 index b301646d6..3b57dcb84 100644 Binary files a/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 and b/src/Modules/Microsoft.PowerShell.Archive/Microsoft.PowerShell.Archive.psm1 differ diff --git a/src/Modules/PSGet/PSGet.Resource.psd1 b/src/Modules/PSGet/PSGet.Resource.psd1 index 7dfaf7c0e..42a789da6 100644 Binary files a/src/Modules/PSGet/PSGet.Resource.psd1 and b/src/Modules/PSGet/PSGet.Resource.psd1 differ diff --git a/src/Modules/PSGet/PSModule.psm1 b/src/Modules/PSGet/PSModule.psm1 index 7f8adb3d9..35c2b88cc 100644 --- a/src/Modules/PSGet/PSModule.psm1 +++ b/src/Modules/PSGet/PSModule.psm1 @@ -79,6 +79,10 @@ $script:PackageManagementProviderParam = "PackageManagementProvider" $script:PublishLocation = "PublishLocation" $script:ScriptSourceLocation = 'ScriptSourceLocation' $script:ScriptPublishLocation = 'ScriptPublishLocation' +$script:Proxy = 'Proxy' +$script:ProxyCredential = 'ProxyCredential' +$script:Credential = 'Credential' +$script:VSTSAuthenticatedFeedsDocUrl = 'https://go.microsoft.com/fwlink/?LinkID=698608' $script:NuGetProviderName = "NuGet" $script:NuGetProviderVersion = [Version]'2.8.5.201' @@ -271,8 +275,8 @@ function PackageManagementMessageResolver($MsgID, $Message) { if($Message) { $tempMessage = $Message -creplace "PackageSource", "PSRepository" - $tempMessage = $tempMessage -creplace "packagesource", "psrepository" - $tempMessage = $tempMessage -creplace "Package", "Module" + $tempMessage = $tempMessage -creplace "packagesource", "psrepository" + $tempMessage = $tempMessage -creplace "Package", "Module" $tempMessage = $tempMessage -creplace "package", "module" $tempMessage = $tempMessage -creplace "Sources", "Repositories" $tempMessage = $tempMessage -creplace "sources", "repositories" @@ -369,8 +373,8 @@ function PackageManagementMessageResolverForScripts($MsgID, $Message) { if($Message) { $tempMessage = $Message -creplace "PackageSource", "PSRepository" - $tempMessage = $tempMessage -creplace "packagesource", "psrepository" - $tempMessage = $tempMessage -creplace "Package", "Script" + $tempMessage = $tempMessage -creplace "packagesource", "psrepository" + $tempMessage = $tempMessage -creplace "Package", "Script" $tempMessage = $tempMessage -creplace "package", "script" $tempMessage = $tempMessage -creplace "Sources", "Repositories" $tempMessage = $tempMessage -creplace "sources", "repositories" @@ -387,7 +391,7 @@ function PackageManagementMessageResolverForScripts($MsgID, $Message) { Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PSGet.Resource.psd1 -#region Add .Net type for Telemetry APIs +#region Add .Net type for Telemetry APIs and WebProxy # This code is required to add a .Net type and call the Telemetry APIs # This is required since PowerShell does not support generation of .Net Anonymous types @@ -398,6 +402,7 @@ $requiredAssembly = ( $source = @" using System; +using System.Net; using System.Management.Automation; namespace Microsoft.PowerShell.Get @@ -414,6 +419,47 @@ namespace Microsoft.PowerShell.Get Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.TraceMessage(operationName, new { SourceLocationType = sourceLocationType, SourceLocationHash = sourceLocationHash, InstallationPolicy = installationPolicy, PackageManagementProvider = packageManagementProvider, PublishLocationHash = publishLocationHash, ScriptSourceLocationHash = scriptSourceLocationHash, ScriptPublishLocationHash = scriptPublishLocationHash }); } + } + + /// + /// Used by Ping-Endpoint function to supply webproxy to HttpClient + /// We cannot use System.Net.WebProxy because this is not available on CoreClr + /// + public class InternalWebProxy : IWebProxy + { + Uri _proxyUri; + ICredentials _credentials; + + public InternalWebProxy(Uri uri, ICredentials credentials) + { + Credentials = credentials; + _proxyUri = uri; + } + + /// + /// Credentials used by WebProxy + /// + public ICredentials Credentials + { + get + { + return _credentials; + } + set + { + _credentials = value; + } + } + + public Uri GetProxy(Uri destination) + { + return _proxyUri; + } + + public bool IsBypassed(Uri host) + { + return false; + } } } "@ @@ -484,6 +530,10 @@ function Publish-Module [string] $Repository = $Script:PSGalleryModuleSource, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + [Parameter()] [ValidateSet("1.0")] [Version] @@ -511,11 +561,11 @@ function Publish-Module [Parameter()] [ValidateNotNullOrEmpty()] [Uri] - $ProjectUri, - - [Parameter()] - [switch] - $Force + $ProjectUri, + + [Parameter()] + [switch] + $Force ) Begin @@ -565,7 +615,7 @@ function Publish-Module -ExceptionObject $ProjectUri } - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe -Force:$Force + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe -Force:$Force } Process @@ -853,15 +903,24 @@ function Publish-Module -ExceptionObject $moduleName } + $FindParameters = @{ + Name = $moduleName + Repository = $Repository + Tag = 'PSScript' + Verbose = $VerbosePreference + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + Debug = $DebugPreference + } + + if($Credential) + { + $FindParameters[$script:Credential] = $Credential + } + # Check if the specified module name is already used for a script on the specified repository # Use Find-Script to check if that name is already used as scriptname - $scriptPSGetItemInfo = Find-Script -Name $moduleName ` - -Repository $Repository ` - -Tag 'PSScript' ` - -Verbose:$VerbosePreference ` - -ErrorAction SilentlyContinue ` - -WarningAction SilentlyContinue ` - -Debug:$DebugPreference | + $scriptPSGetItemInfo = Find-Script @FindParameters | Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $moduleName} | Microsoft.PowerShell.Utility\Select-Object -Last 1 if($scriptPSGetItemInfo) @@ -875,12 +934,8 @@ function Publish-Module -ExceptionObject $moduleName } - $currentPSGetItemInfo = Find-Module -Name $moduleInfo.Name ` - -Repository $Repository ` - -Verbose:$VerbosePreference ` - -ErrorAction SilentlyContinue ` - -WarningAction SilentlyContinue ` - -Debug:$DebugPreference | + $null = $FindParameters.Remove('Tag') + $currentPSGetItemInfo = Find-Module @FindParameters | Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $moduleInfo.Name} | Microsoft.PowerShell.Utility\Select-Object -Last 1 @@ -895,7 +950,7 @@ function Publish-Module } $shouldProcessMessage = $LocalizedData.PublishModulewhatIfMessage -f ($moduleInfo.Version, $moduleInfo.Name) - if($Force -or $PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Module")) + if($Force -or $PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Module")) { Publish-PSArtifactUtility -PSModuleInfo $moduleInfo ` -ManifestPath $manifestPath ` @@ -991,17 +1046,30 @@ function Find-Module [string[]] $Command, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [ValidateNotNullOrEmpty()] [string[]] - $Repository + $Repository, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential ) Begin { Get-PSGalleryApiAvailability -Repository $Repository - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential } Process @@ -1156,6 +1224,19 @@ function Save-Module [string] $LiteralPath, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + [Parameter()] [switch] $Force @@ -1165,7 +1246,7 @@ function Save-Module { Get-PSGalleryApiAvailability -Repository $Repository - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential # Module names already tried in the current pipeline for InputObject parameterset $moduleNamesInPipeline = @() @@ -1359,11 +1440,24 @@ function Install-Module [string[]] $Repository, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + [Parameter()] [ValidateSet("CurrentUser","AllUsers")] [string] $Scope = "AllUsers", + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [switch] $Force @@ -1385,7 +1479,7 @@ function Install-Module -ErrorCategory InvalidArgument } - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential # Module names already tried in the current pipeline for InputObject parameterset $moduleNamesInPipeline = @() @@ -1569,6 +1663,19 @@ function Update-Module [Version] $MaximumVersion, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [Switch] $Force @@ -1576,7 +1683,7 @@ function Update-Module Begin { - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential # Module names already tried in the current pipeline $moduleNamesInPipeline = @() @@ -1914,6 +2021,15 @@ function Find-DscResource [string] $Filter, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [ValidateNotNullOrEmpty()] [string[]] @@ -2008,6 +2124,15 @@ function Find-Command [string] $Filter, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [ValidateNotNullOrEmpty()] [string[]] @@ -2104,6 +2229,15 @@ function Find-RoleCapability [string] $Filter, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [ValidateNotNullOrEmpty()] [string[]] @@ -2187,11 +2321,15 @@ function Publish-Script [Parameter()] [ValidateNotNullOrEmpty()] [string] - $Repository = $Script:PSGalleryModuleSource, - - [Parameter()] - [switch] - $Force + $Repository = $Script:PSGalleryModuleSource, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + + [Parameter()] + [switch] + $Force ) Begin @@ -2208,7 +2346,7 @@ function Publish-Script Get-PSGalleryApiAvailability -Repository $Repository - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe -Force:$Force + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -BootstrapNuGetExe -Force:$Force } Process @@ -2369,15 +2507,24 @@ function Publish-Script try { + $FindParameters = @{ + Name = $scriptName + Repository = $Repository + Tag = 'PSModule' + Verbose = $VerbosePreference + ErrorAction = 'SilentlyContinue' + WarningAction = 'SilentlyContinue' + Debug = $DebugPreference + } + + if($Credential) + { + $FindParameters[$script:Credential] = $Credential + } + # Check if the specified script name is already used for a module on the specified repository # Use Find-Module to check if that name is already used as module name - $modulePSGetItemInfo = Find-Module -Name $scriptName ` - -Repository $Repository ` - -Tag 'PSModule' ` - -Verbose:$VerbosePreference ` - -ErrorAction SilentlyContinue ` - -WarningAction SilentlyContinue ` - -Debug:$DebugPreference | + $modulePSGetItemInfo = Find-Module @FindParameters | Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $scriptName} | Microsoft.PowerShell.Utility\Select-Object -Last 1 if($modulePSGetItemInfo) @@ -2391,13 +2538,10 @@ function Publish-Script -ExceptionObject $scriptName } + $null = $FindParameters.Remove('Tag') + $currentPSGetItemInfo = $null - $currentPSGetItemInfo = Find-Script -Name $scriptName ` - -Repository $Repository ` - -Verbose:$VerbosePreference ` - -ErrorAction SilentlyContinue ` - -WarningAction SilentlyContinue ` - -Debug:$DebugPreference | + $currentPSGetItemInfo = Find-Script @FindParameters | Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $scriptName} | Microsoft.PowerShell.Utility\Select-Object -Last 1 @@ -2415,7 +2559,7 @@ function Publish-Script } $shouldProcessMessage = $LocalizedData.PublishScriptwhatIfMessage -f ($PSScriptInfo.Version, $scriptName) - if($Force -or $PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Script")) + if($Force -or $PSCmdlet.ShouldProcess($shouldProcessMessage, "Publish-Script")) { Publish-PSArtifactUtility -PSScriptInfo $PSScriptInfo ` -NugetApiKey $NuGetApiKey ` @@ -2494,17 +2638,30 @@ function Find-Script [string[]] $Command, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [ValidateNotNullOrEmpty()] [string[]] - $Repository + $Repository, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential ) Begin { Get-PSGalleryApiAvailability -Repository $Repository - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential } Process @@ -2690,6 +2847,19 @@ function Save-Script [string] $LiteralPath, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + [Parameter()] [switch] $Force @@ -2699,7 +2869,7 @@ function Save-Script { Get-PSGalleryApiAvailability -Repository $Repository - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential # Script names already tried in the current pipeline for InputObject parameterset $scriptNamesInPipeline = @() @@ -2905,7 +3075,20 @@ function Install-Script [Parameter()] [Switch] $NoPathUpdate, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + [Parameter()] [switch] $Force @@ -2943,7 +3126,7 @@ function Install-Script -NoPathUpdate:$NoPathUpdate ` -Force:$Force - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential # Script names already tried in the current pipeline for InputObject parameterset $scriptNamesInPipeline = @() @@ -3175,6 +3358,19 @@ function Update-Script [Version] $MaximumVersion, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $Credential, + [Parameter()] [Switch] $Force @@ -3182,7 +3378,7 @@ function Update-Script Begin { - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential # Script names already tried in the current pipeline $scriptNamesInPipeline = @() @@ -3500,50 +3696,59 @@ function Register-PSRepository <# .ExternalHelp PSGet.psm1-help.xml #> - [CmdletBinding(DefaultParameterSetName='NameParameterSet', + [CmdletBinding(DefaultParameterSetName='NameParameterSet', HelpUri='http://go.microsoft.com/fwlink/?LinkID=517129')] Param ( - [Parameter(Mandatory=$true, - Position=0, - ParameterSetName='NameParameterSet')] + [Parameter(Mandatory=$true, + Position=0, + ParameterSetName='NameParameterSet')] [ValidateNotNullOrEmpty()] [string] $Name, - [Parameter(Mandatory=$true, - Position=1, - ParameterSetName='NameParameterSet')] + [Parameter(Mandatory=$true, + Position=1, + ParameterSetName='NameParameterSet')] [ValidateNotNullOrEmpty()] [Uri] $SourceLocation, - [Parameter(ParameterSetName='NameParameterSet')] + [Parameter(ParameterSetName='NameParameterSet')] [ValidateNotNullOrEmpty()] [Uri] $PublishLocation, - [Parameter(ParameterSetName='NameParameterSet')] + [Parameter(ParameterSetName='NameParameterSet')] [ValidateNotNullOrEmpty()] [Uri] $ScriptSourceLocation, - [Parameter(ParameterSetName='NameParameterSet')] + [Parameter(ParameterSetName='NameParameterSet')] [ValidateNotNullOrEmpty()] [Uri] $ScriptPublishLocation, - [Parameter(Mandatory=$true, - ParameterSetName='PSGalleryParameterSet')] - [Switch] - $Default, - + [Parameter(Mandatory=$true, + ParameterSetName='PSGalleryParameterSet')] + [Switch] + $Default, + [Parameter()] [ValidateSet('Trusted','Untrusted')] [string] $InstallationPolicy = 'Untrusted', - [Parameter(ParameterSetName='NameParameterSet')] + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + + [Parameter(ParameterSetName='NameParameterSet')] [ValidateNotNullOrEmpty()] [string] $PackageManagementProvider @@ -3573,7 +3778,7 @@ function Register-PSRepository { Get-PSGalleryApiAvailability -Repository $Name - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential if($PackageManagementProvider) { @@ -3602,86 +3807,88 @@ function Register-PSRepository Process { - if($PSCmdlet.ParameterSetName -eq 'PSGalleryParameterSet') + if($PSCmdlet.ParameterSetName -eq 'PSGalleryParameterSet') { - if(-not $Default) - { - return - } + if(-not $Default) + { + return + } - $PSBoundParameters['Name'] = $Script:PSGalleryModuleSource - $null = $PSBoundParameters.Remove('Default') + $PSBoundParameters['Name'] = $Script:PSGalleryModuleSource + $null = $PSBoundParameters.Remove('Default') } else { - if($Name -eq $Script:PSGalleryModuleSource) - { - $message = $LocalizedData.UseDefaultParameterSetOnRegisterPSRepository - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId 'UseDefaultParameterSetOnRegisterPSRepository' ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument ` - -ExceptionObject $Name - return - } + if($Name -eq $Script:PSGalleryModuleSource) + { + $message = $LocalizedData.UseDefaultParameterSetOnRegisterPSRepository + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'UseDefaultParameterSetOnRegisterPSRepository' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Name + return + } - # Ping and resolve the specified location - $SourceLocation = Resolve-Location -Location (Get-LocationString -LocationUri $SourceLocation) ` - -LocationParameterName 'SourceLocation' ` - -CallerPSCmdlet $PSCmdlet - if(-not $SourceLocation) - { - # Above Resolve-Location function throws an error when it is not able to resolve a location - return - } + # Ping and resolve the specified location + $SourceLocation = Resolve-Location -Location (Get-LocationString -LocationUri $SourceLocation) ` + -LocationParameterName 'SourceLocation' ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` + -CallerPSCmdlet $PSCmdlet + if(-not $SourceLocation) + { + # Above Resolve-Location function throws an error when it is not able to resolve a location + return + } - $providerName = $null + $providerName = $null - if($PackageManagementProvider) - { - $providerName = $PackageManagementProvider - } - elseif($selctedProviderName) - { - $providerName = $selctedProviderName - } - else - { - $providerName = Get-PackageManagementProviderName -Location $SourceLocation - } - - if($providerName) - { - $PSBoundParameters[$script:PackageManagementProviderParam] = $providerName - } - - if($PublishLocation) - { - $PSBoundParameters[$script:PublishLocation] = Get-LocationString -LocationUri $PublishLocation - } - - if($ScriptPublishLocation) - { - $PSBoundParameters[$script:ScriptPublishLocation] = Get-LocationString -LocationUri $ScriptPublishLocation - } - - if($ScriptSourceLocation) - { - $PSBoundParameters[$script:ScriptSourceLocation] = Get-LocationString -LocationUri $ScriptSourceLocation - } - - $PSBoundParameters["Location"] = Get-LocationString -LocationUri $SourceLocation - $null = $PSBoundParameters.Remove("SourceLocation") + if($PackageManagementProvider) + { + $providerName = $PackageManagementProvider + } + elseif($selctedProviderName) + { + $providerName = $selctedProviderName + } + else + { + $providerName = Get-PackageManagementProviderName -Location $SourceLocation + } + + if($providerName) + { + $PSBoundParameters[$script:PackageManagementProviderParam] = $providerName + } + + if($PublishLocation) + { + $PSBoundParameters[$script:PublishLocation] = Get-LocationString -LocationUri $PublishLocation + } + + if($ScriptPublishLocation) + { + $PSBoundParameters[$script:ScriptPublishLocation] = Get-LocationString -LocationUri $ScriptPublishLocation + } + + if($ScriptSourceLocation) + { + $PSBoundParameters[$script:ScriptSourceLocation] = Get-LocationString -LocationUri $ScriptSourceLocation + } + + $PSBoundParameters["Location"] = Get-LocationString -LocationUri $SourceLocation + $null = $PSBoundParameters.Remove("SourceLocation") } - if($InstallationPolicy -eq "Trusted") + if($InstallationPolicy -eq "Trusted") { - $PSBoundParameters['Trusted'] = $true + $PSBoundParameters['Trusted'] = $true } $null = $PSBoundParameters.Remove("InstallationPolicy") - $PSBoundParameters["Provider"] = $script:PSModuleProviderName + $PSBoundParameters["Provider"] = $script:PSModuleProviderName $PSBoundParameters["MessageResolver"] = $script:PackageManagementMessageResolverScriptBlock $null = PackageManagement\Register-PackageSource @PSBoundParameters @@ -3697,12 +3904,12 @@ function Set-PSRepository HelpUri='http://go.microsoft.com/fwlink/?LinkID=517128')] Param ( - [Parameter(Mandatory=$true, Position=0)] + [Parameter(Mandatory=$true, Position=0)] [ValidateNotNullOrEmpty()] [string] $Name, - [Parameter(Position=1)] + [Parameter(Position=1)] [ValidateNotNullOrEmpty()] [Uri] $SourceLocation, @@ -3727,6 +3934,15 @@ function Set-PSRepository [string] $InstallationPolicy, + [Parameter(ValueFromPipelineByPropertyName=$true)] + [ValidateNotNullOrEmpty()] + [Uri] + $Proxy, + + [Parameter(ValueFromPipelineByPropertyName=$true)] + [PSCredential] + $ProxyCredential, + [Parameter()] [ValidateNotNullOrEmpty()] [string] @@ -3764,7 +3980,7 @@ function Set-PSRepository { Get-PSGalleryApiAvailability -Repository $Name - Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet + Install-NuGetClientBinaries -CallerPSCmdlet $PSCmdlet -Proxy $Proxy -ProxyCredential $ProxyCredential if($PackageManagementProvider) { @@ -3799,6 +4015,8 @@ function Set-PSRepository # Ping and resolve the specified location $SourceLocation = Resolve-Location -Location (Get-LocationString -LocationUri $SourceLocation) ` -LocationParameterName 'SourceLocation' ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` -CallerPSCmdlet $PSCmdlet if(-not $SourceLocation) { @@ -5431,6 +5649,27 @@ function Get-ScriptCommentHelpInfoString #endregion *-ScriptFileInfo cmdlets #region Utility functions +function Get-ParametersHashtable +{ + param( + $Proxy, + $ProxyCredential + ) + + $ParametersHashtable = @{} + if($Proxy) + { + $ParametersHashtable[$script:Proxy] = $Proxy + } + + if($ProxyCredential) + { + $ParametersHashtable[$script:ProxyCredential] = $ProxyCredential + } + + return $ParametersHashtable +} + function ToUpper { param([string]$str) @@ -5629,6 +5868,7 @@ function HttpClientApisAvailable function Ping-Endpoint { + [CmdletBinding()] param ( [Parameter()] @@ -5636,6 +5876,12 @@ function Ping-Endpoint [string] $Endpoint, + [Parameter()] + $Proxy, + + [Parameter()] + $ProxyCredential, + [Parameter()] [switch] $AllowAutoRedirect = $true @@ -5643,6 +5889,18 @@ function Ping-Endpoint $results = @{} + $WebProxy = $null + if($Proxy) + { + $ProxyNetworkCredential = $null + if($ProxyCredential) + { + $ProxyNetworkCredential = $ProxyCredential.GetNetworkCredential() + } + + $WebProxy = New-Object Microsoft.PowerShell.Get.InternalWebProxy -ArgumentList $Proxy,$ProxyNetworkCredential + } + if(HttpClientApisAvailable) { $response = $null @@ -5650,6 +5908,12 @@ function Ping-Endpoint { $handler = New-Object System.Net.Http.HttpClientHandler $handler.UseDefaultCredentials = $true + + if($WebProxy) + { + $handler.Proxy = $WebProxy + } + $httpClient = New-Object System.Net.Http.HttpClient -ArgumentList $handler $response = $httpclient.GetAsync($endpoint) } @@ -5671,6 +5935,8 @@ function Ping-Endpoint $iss.LanguageMode = "FullLanguage" $WebRequestcmd = @' + param($WebProxy) + try {{ $request = [System.Net.WebRequest]::Create("{0}") @@ -5678,6 +5944,12 @@ function Ping-Endpoint $request.Timeout = 30000 $request.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials $request.AllowAutoRedirect = ${1} + + if($WebProxy) + {{ + $request.Proxy = $WebProxy + }} + $response = [System.Net.HttpWebResponse]$request.GetResponse() if($response.StatusCode.value__ -eq 302) {{ @@ -5696,6 +5968,12 @@ function Ping-Endpoint '@ -f $EndPoint, $AllowAutoRedirect $ps = [powershell]::Create($iss).AddScript($WebRequestcmd) + + if($WebProxy) + { + $ps.AddParameter($WebProxy) + } + $response = $ps.Invoke() $ps.dispose() if ($response -ne "Error:System.Net.WebException") @@ -5965,54 +6243,75 @@ function Set-PSGetSettingsVariable } } -function Set-PSGalleryRepository -{ - [CmdletBinding()] - param([switch]$Trusted) - - $psgalleryLocation = Resolve-Location -Location $Script:PSGallerySourceUri ` - -LocationParameterName 'SourceLocation' ` - -ErrorAction SilentlyContinue ` - -WarningAction SilentlyContinue - - $scriptSourceLocation = Resolve-Location -Location $Script:PSGalleryScriptSourceUri ` - -LocationParameterName 'ScriptSourceLocation' ` - -ErrorAction SilentlyContinue ` - -WarningAction SilentlyContinue - if($psgalleryLocation) - { - $result = Ping-Endpoint -Endpoint $Script:PSGalleryPublishUri -AllowAutoRedirect:$false - if ($result.ContainsKey($Script:ResponseUri) -and $result[$Script:ResponseUri]) - { - $script:PSGalleryPublishUri = $result[$Script:ResponseUri] - } - - $repository = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ - Name = $Script:PSGalleryModuleSource - SourceLocation = $psgalleryLocation - PublishLocation = $Script:PSGalleryPublishUri - ScriptSourceLocation = $scriptSourceLocation - ScriptPublishLocation = $Script:PSGalleryPublishUri - Trusted=$Trusted - Registered=$true - InstallationPolicy = if($Trusted) {'Trusted'} else {'Untrusted'} - PackageManagementProvider=$script:NuGetProviderName - ProviderOptions = @{} - }) - - $repository.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSRepository") - $script:PSGetModuleSources[$Script:PSGalleryModuleSource] = $repository - - Save-ModuleSources - - return $repository - } -} - +function Set-PSGalleryRepository +{ + [CmdletBinding()] + param( + [Parameter()] + [switch] + $Trusted, + + [Parameter()] + $Proxy, + + [Parameter()] + $ProxyCredential + ) + + $psgalleryLocation = Resolve-Location -Location $Script:PSGallerySourceUri ` + -LocationParameterName 'SourceLocation' ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + + $scriptSourceLocation = Resolve-Location -Location $Script:PSGalleryScriptSourceUri ` + -LocationParameterName 'ScriptSourceLocation' ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` + -ErrorAction SilentlyContinue ` + -WarningAction SilentlyContinue + if($psgalleryLocation) + { + $result = Ping-Endpoint -Endpoint $Script:PSGalleryPublishUri -AllowAutoRedirect:$false -Proxy $Proxy -ProxyCredential $ProxyCredential + if ($result.ContainsKey($Script:ResponseUri) -and $result[$Script:ResponseUri]) + { + $script:PSGalleryPublishUri = $result[$Script:ResponseUri] + } + + $repository = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ + Name = $Script:PSGalleryModuleSource + SourceLocation = $psgalleryLocation + PublishLocation = $Script:PSGalleryPublishUri + ScriptSourceLocation = $scriptSourceLocation + ScriptPublishLocation = $Script:PSGalleryPublishUri + Trusted=$Trusted + Registered=$true + InstallationPolicy = if($Trusted) {'Trusted'} else {'Untrusted'} + PackageManagementProvider=$script:NuGetProviderName + ProviderOptions = @{} + }) + + $repository.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSRepository") + $script:PSGetModuleSources[$Script:PSGalleryModuleSource] = $repository + + Save-ModuleSources + + return $repository + } +} + function Set-ModuleSourcesVariable { [CmdletBinding()] - param([switch]$Force) + param( + [switch] + $Force, + + $Proxy, + + $ProxyCredential + ) if(-not $script:PSGetModuleSources -or $Force) { @@ -6027,7 +6326,7 @@ function Set-ModuleSourcesVariable if(-not $script:PSGetModuleSources.Contains($Script:PSGalleryModuleSource)) { - $null = Set-PSGalleryRepository + $null = Set-PSGalleryRepository -Proxy $Proxy -ProxyCredential $ProxyCredential } } @@ -6039,7 +6338,7 @@ function Set-ModuleSourcesVariable if(-not (Get-Member -InputObject $moduleSource -Name $script:ScriptSourceLocation)) { - $scriptSourceLocation = Get-ScriptSourceLocation -Location $moduleSource.SourceLocation + $scriptSourceLocation = Get-ScriptSourceLocation -Location $moduleSource.SourceLocation -Proxy $Proxy -ProxyCredential $ProxyCredential Microsoft.PowerShell.Utility\Add-Member -InputObject $script:PSGetModuleSources[$_] ` -MemberType NoteProperty ` @@ -6584,6 +6883,12 @@ function Install-NuGetClientBinaries [switch] $BootstrapNuGetExe, + [Parameter()] + $Proxy, + + [Parameter()] + $ProxyCredential, + [parameter()] [switch] $Force @@ -6715,6 +7020,8 @@ function Install-NuGetClientBinaries $shouldContinueCaption = $LocalizedData.InstallNuGetExeShouldContinueCaption } + $AdditionalParams = Get-ParametersHashtable -Proxy $Proxy -ProxyCredential $ProxyCredential + if($Force -or $psCmdlet.ShouldContinue($shouldContinueQueryMessage, $shouldContinueCaption)) { if($bootstrapNuGetProvider) @@ -6731,7 +7038,7 @@ function Install-NuGetClientBinaries $null = PackageManagement\Install-PackageProvider -Name $script:NuGetProviderName ` -MinimumVersion $script:NuGetProviderVersion ` -Scope $scope ` - -Force + -Force @AdditionalParams # Force import ensures that nuget provider with minimum version got loaded. $null = PackageManagement\Import-PackageProvider -Name $script:NuGetProviderName ` @@ -6772,7 +7079,8 @@ function Install-NuGetClientBinaries # Download the NuGet.exe from http://nuget.org/NuGet.exe $null = Microsoft.PowerShell.Utility\Invoke-WebRequest -Uri $script:NuGetClientSourceURL ` - -OutFile $nugetExeFilePath + -OutFile $nugetExeFilePath ` + @AdditionalParams if (Microsoft.PowerShell.Management\Test-Path -Path $nugetExeFilePath) { @@ -7604,7 +7912,7 @@ function Publish-PSArtifactUtility Microsoft.PowerShell.Management\Remove-Item $NupkgPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false - Microsoft.PowerShell.Management\Set-Content -Value $nuspec -Path $NuspecPath -Force -Confirm:$false -WhatIf:$false + Microsoft.PowerShell.Management\Set-Content -Value $nuspec -Path $NuspecPath -Force -Confirm:$false -WhatIf:$false # Create .nupkg file $output = & $script:NuGetExePath pack $NuspecPath -OutputDirectory $NugetPackageRoot @@ -7640,6 +7948,12 @@ function Publish-PSArtifactUtility if($errorMsg) { + if(($NugetApiKey -eq 'VSTS') -and + ($errorMsg -match 'Cannot prompt for input in non-interactive mode.') ) + { + $errorMsg = $LocalizedData.RegisterVSTSFeedAsNuGetPackageSource -f ($Destination, $script:VSTSAuthenticatedFeedsDocUrl) + } + if($PSArtifactType -eq $script:PSArtifactTypeModule) { $message = $LocalizedData.FailedToPublish -f ($Name,$errorMsg) @@ -8005,11 +8319,11 @@ function Get-DynamicOptions switch($category) { Package { - Write-Output -InputObject (New-DynamicOption -Category $category ` - -Name $script:PSArtifactType ` - -ExpectedType String ` - -IsRequired $false ` - -PermittedValues @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript, $script:All)) + Write-Output -InputObject (New-DynamicOption -Category $category ` + -Name $script:PSArtifactType ` + -ExpectedType String ` + -IsRequired $false ` + -PermittedValues @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript, $script:All)) Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:Filter -ExpectedType String -IsRequired $false) Write-Output -InputObject (New-DynamicOption -Category $category -Name $script:Tag -ExpectedType StringArray -IsRequired $false) Write-Output -InputObject (New-DynamicOption -Category $category -Name Includes -ExpectedType StringArray -IsRequired $false -PermittedValues $script:IncludeValidSet) @@ -8026,11 +8340,11 @@ function Get-DynamicOptions Install { - Write-Output -InputObject (New-DynamicOption -Category $category ` - -Name $script:PSArtifactType ` - -ExpectedType String ` - -IsRequired $false ` - -PermittedValues @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript, $script:All)) + Write-Output -InputObject (New-DynamicOption -Category $category ` + -Name $script:PSArtifactType ` + -ExpectedType String ` + -IsRequired $false ` + -PermittedValues @($script:PSArtifactTypeModule,$script:PSArtifactTypeScript, $script:All)) Write-Output -InputObject (New-DynamicOption -Category $category -Name "Scope" -ExpectedType String -IsRequired $false -PermittedValues @("CurrentUser","AllUsers")) Write-Output -InputObject (New-DynamicOption -Category $category -Name "InstallUpdate" -ExpectedType Switch -IsRequired $false) Write-Output -InputObject (New-DynamicOption -Category $category -Name 'NoPathUpdate' -ExpectedType Switch -IsRequired $false) @@ -8055,13 +8369,11 @@ function Add-PackageSource Write-Debug ($LocalizedData.ProviderApiDebugMessage -f ('Add-PackageSource')) - Set-ModuleSourcesVariable -Force + if(-not $Name) + { + return + } - if(-not $Name) - { - return - } - $IsNewModuleSource = $false $Options = $request.Options @@ -8070,6 +8382,31 @@ function Add-PackageSource Write-Debug ( "OPTION: {0} => {1}" -f ($o, $Options[$o]) ) } + $Proxy = $null + if($Options.ContainsKey($script:Proxy)) + { + $Proxy = $Options[$script:Proxy] + + if(-not (Test-WebUri -Uri $Proxy)) + { + $message = $LocalizedData.InvalidWebUri -f ($Proxy, $script:Proxy) + ThrowError -ExceptionName 'System.ArgumentException' ` + -ExceptionMessage $message ` + -ErrorId 'InvalidWebUri' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Proxy + } + } + + $ProxyCredential = $null + if($Options.ContainsKey($script:ProxyCredential)) + { + $ProxyCredential = $Options[$script:ProxyCredential] + } + + Set-ModuleSourcesVariable -Force -Proxy $Proxy -ProxyCredential $ProxyCredential + if($Options.ContainsKey('IsNewModuleSource')) { $IsNewModuleSource = $Options['IsNewModuleSource'] @@ -8108,17 +8445,17 @@ function Add-PackageSource $PublishLocation = $null if($Options.ContainsKey($script:PublishLocation)) { - if($Name -eq $Script:PSGalleryModuleSource) - { - $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('PublishLocation') - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument ` - -ExceptionObject $PublishLocation - } - + if($Name -eq $Script:PSGalleryModuleSource) + { + $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('PublishLocation') + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $PublishLocation + } + $PublishLocation = $Options[$script:PublishLocation] if(-not (Microsoft.PowerShell.Management\Test-Path $PublishLocation) -and @@ -8151,17 +8488,17 @@ function Add-PackageSource $ScriptSourceLocation = $null if($Options.ContainsKey($script:ScriptSourceLocation)) { - if($Name -eq $Script:PSGalleryModuleSource) - { - $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('ScriptSourceLocation') - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument ` - -ExceptionObject $ScriptSourceLocation - } - + if($Name -eq $Script:PSGalleryModuleSource) + { + $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('ScriptSourceLocation') + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ScriptSourceLocation + } + $ScriptSourceLocation = $Options[$script:ScriptSourceLocation] if(-not (Microsoft.PowerShell.Management\Test-Path $ScriptSourceLocation) -and @@ -8194,17 +8531,17 @@ function Add-PackageSource $ScriptPublishLocation = $null if($Options.ContainsKey($script:ScriptPublishLocation)) { - if($Name -eq $Script:PSGalleryModuleSource) - { - $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('ScriptPublishLocation') - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument ` - -ExceptionObject $ScriptPublishLocation - } - + if($Name -eq $Script:PSGalleryModuleSource) + { + $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('ScriptPublishLocation') + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $ScriptPublishLocation + } + $ScriptPublishLocation = $Options[$script:ScriptPublishLocation] if(-not (Microsoft.PowerShell.Management\Test-Path $ScriptPublishLocation) -and @@ -8234,53 +8571,55 @@ function Add-PackageSource } } - $currentSourceObject = $null - - # Check if Name is already registered - if($script:PSGetModuleSources.Contains($Name)) - { - $currentSourceObject = $script:PSGetModuleSources[$Name] - } - - # Location is not allowed for PSGallery source - # However OneGet passes Location value during Set-PackageSource cmdlet, - # that's why ensuring that Location value is same as the current SourceLocation - # - if(($Name -eq $Script:PSGalleryModuleSource) -and - $Location -and - ((-not $IsUpdatePackageSource) -or ($currentSourceObject -and $currentSourceObject.SourceLocation -ne $Location))) - { - $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('Location, NewLocation or SourceLocation') - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument ` - -ExceptionObject $Location - } - - if($Name -eq $Script:PSGalleryModuleSource) - { - # Add or update the PSGallery repostory - $repository = Set-PSGalleryRepository -Trusted:$Trusted - - if($repository) - { - # return the package source object. - Write-Output -InputObject (New-PackageSourceFromModuleSource -ModuleSource $repository) - } - - return - } - - if($Location) - { - # Ping and resolve the specified location - $Location = Resolve-Location -Location $Location ` - -LocationParameterName 'Location' ` - -CallerPSCmdlet $PSCmdlet - } - + $currentSourceObject = $null + + # Check if Name is already registered + if($script:PSGetModuleSources.Contains($Name)) + { + $currentSourceObject = $script:PSGetModuleSources[$Name] + } + + # Location is not allowed for PSGallery source + # However OneGet passes Location value during Set-PackageSource cmdlet, + # that's why ensuring that Location value is same as the current SourceLocation + # + if(($Name -eq $Script:PSGalleryModuleSource) -and + $Location -and + ((-not $IsUpdatePackageSource) -or ($currentSourceObject -and $currentSourceObject.SourceLocation -ne $Location))) + { + $message = $LocalizedData.ParameterIsNotAllowedWithPSGallery -f ('Location, NewLocation or SourceLocation') + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId 'ParameterIsNotAllowedWithPSGallery' ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument ` + -ExceptionObject $Location + } + + if($Name -eq $Script:PSGalleryModuleSource) + { + # Add or update the PSGallery repostory + $repository = Set-PSGalleryRepository -Trusted:$Trusted + + if($repository) + { + # return the package source object. + Write-Output -InputObject (New-PackageSourceFromModuleSource -ModuleSource $repository) + } + + return + } + + if($Location) + { + # Ping and resolve the specified location + $Location = Resolve-Location -Location $Location ` + -LocationParameterName 'Location' ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` + -CallerPSCmdlet $PSCmdlet + } + if(-not $Location) { # Above Resolve-Location function throws an error when it is not able to resolve a location @@ -8324,7 +8663,7 @@ function Add-PackageSource -ExceptionObject $Name } - $LocationString = Get-ValidModuleLocation -LocationString $Location -ParameterName "Location" + $LocationString = Get-ValidModuleLocation -LocationString $Location -ParameterName "Location" -Proxy $Proxy -ProxyCredential $ProxyCredential # Check if Location is already registered with another Name $existingSourceName = Get-SourceName -Location $LocationString @@ -8413,7 +8752,8 @@ function Add-PackageSource if($SelectedProvider -and $SelectedProvider.Features.ContainsKey($script:SupportsPSModulesFeatureName)) { - $packageSource = $SelectedProvider.ResolvePackageSources( (New-Request -Sources @($LocationString)) ) + $NewRequest = $request.CloneRequest( $null, @($LocationString), $request.Credential ) + $packageSource = $SelectedProvider.ResolvePackageSources( $NewRequest ) } else { @@ -8454,7 +8794,8 @@ function Add-PackageSource } Write-Verbose ($LocalizedData.PollingSingleProviderForLocation -f ($LocationString, $provider.ProviderName)) - $packageSource = $provider.ResolvePackageSources((New-Request -Option @{} -Sources @($LocationString))) + $NewRequest = $request.CloneRequest( @{}, @($LocationString), $request.Credential ) + $packageSource = $provider.ResolvePackageSources($NewRequest) if($packageSource) { @@ -8525,7 +8866,7 @@ function Add-PackageSource if(-not $ScriptSourceLocation) { - $ScriptSourceLocation = Get-ScriptSourceLocation -Location $LocationString + $ScriptSourceLocation = Get-ScriptSourceLocation -Location $LocationString -Proxy $Proxy -ProxyCredential $ProxyCredential } elseif($Options.ContainsKey($script:ScriptSourceLocation)) { @@ -8546,7 +8887,14 @@ function Add-PackageSource } else { - if($ScriptSourceLocation -eq $LocationString) + if($ScriptSourceLocation -eq $LocationString -and + -not ($LocationString.EndsWith('/nuget/v2', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/nuget/v2/', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/nuget', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/nuget/', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('index.json', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('index.json/', [System.StringComparison]::OrdinalIgnoreCase)) + ) { $message = $LocalizedData.SourceLocationUrisForModulesAndScriptsShouldBeDifferent -f ($LocationString, $ScriptSourceLocation) ThrowError -ExceptionName "System.InvalidOperationException" ` @@ -8826,12 +9174,10 @@ function Find-Package else { $message = $LocalizedData.RepositoryNotFound -f ($sourceName) - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId "RepositoryNotFound" ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument ` - -ExceptionObject $sourceName + Write-Error -Message $message ` + -ErrorId 'RepositoryNotFound' ` + -Category InvalidArgument ` + -TargetObject $sourceName } } } @@ -9135,11 +9481,13 @@ function Find-Package $providerOptions["Headers"] = 'PSGalleryClientVersion=1.1' + $NewRequest = $request.CloneRequest( $providerOptions, @($Location), $request.Credential ) + $pkgs = $provider.FindPackages($names, $requiredVersion, $minimumVersion, $maximumVersion, - (New-Request -Sources @($Location) -Options $providerOptions) ) + $NewRequest ) foreach($pkg in $pkgs) { @@ -9754,9 +10102,12 @@ function Install-PackageUtility Write-Verbose ($LocalizedData.SpecifiedLocationAndOGP -f ($provider.ProviderName, $providerName)) - $newRequest = New-Request -Options @{Destination=$tempDestination; - ExcludeVersion=$true} ` - -Sources @($SourceLocation) + $ProviderOptions = @{ + Destination=$tempDestination; + ExcludeVersion=$true + } + + $newRequest = $request.CloneRequest( $ProviderOptions, @($SourceLocation), $request.Credential ) if($artfactType -eq $script:PSArtifactTypeModule) { @@ -10008,11 +10359,11 @@ function Install-PackageUtility if($IsSavePackage) { - $message = $LocalizedData.ModuleSavedSuccessfully -f ($psgItemInfo.Name) + $message = $LocalizedData.ModuleSavedSuccessfully -f ($psgItemInfo.Name, $installLocation) } else { - $message = $LocalizedData.ModuleInstalledSuccessfully -f ($psgItemInfo.Name) + $message = $LocalizedData.ModuleInstalledSuccessfully -f ($psgItemInfo.Name, $installLocation) } Write-Verbose $message } @@ -10135,11 +10486,11 @@ function Install-PackageUtility if($IsSavePackage) { - $message = $LocalizedData.ScriptSavedSuccessfully -f ($psgItemInfo.Name) + $message = $LocalizedData.ScriptSavedSuccessfully -f ($psgItemInfo.Name, $installLocation) } else { - $message = $LocalizedData.ScriptInstalledSuccessfully -f ($psgItemInfo.Name) + $message = $LocalizedData.ScriptInstalledSuccessfully -f ($psgItemInfo.Name, $installLocation) } Write-Verbose $message } @@ -11032,7 +11383,7 @@ function New-SoftwareIdentityFromPSGetItemInfo } $entities = New-Object -TypeName System.Collections.ArrayList - if($psgetItemInfo.Author) + if($psgetItemInfo.Author -and $psgetItemInfo.Author.ToString()) { $entities.Add( (New-Entity -Name $psgetItemInfo.Author -Role 'author') ) } @@ -11373,7 +11724,13 @@ function Get-ValidModuleLocation [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] - $ParameterName + $ParameterName, + + [Parameter()] + $Proxy, + + [Parameter()] + $ProxyCredential ) # Get the actual Uri from the Location @@ -11381,6 +11738,12 @@ function Get-ValidModuleLocation { # Append '/api/v2/' to the $LocationString, return if that URI works. if(($LocationString -notmatch 'LinkID') -and + -not ($LocationString.EndsWith('/nuget/v2', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/nuget/v2/', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/nuget', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('/nuget/', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('index.json', [System.StringComparison]::OrdinalIgnoreCase)) -and + -not ($LocationString.EndsWith('index.json/', [System.StringComparison]::OrdinalIgnoreCase)) -and -not ($LocationString.EndsWith('/api/v2', [System.StringComparison]::OrdinalIgnoreCase)) -and -not ($LocationString.EndsWith('/api/v2/', [System.StringComparison]::OrdinalIgnoreCase)) ) @@ -11401,6 +11764,8 @@ function Get-ValidModuleLocation # Ping and resolve the specified location $tempLocation = Resolve-Location -Location $tempLocation ` -LocationParameterName $ParameterName ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` -ErrorAction SilentlyContinue ` -WarningAction SilentlyContinue if($tempLocation) @@ -11414,6 +11779,8 @@ function Get-ValidModuleLocation # Ping and resolve the specified location $LocationString = Resolve-Location -Location $LocationString ` -LocationParameterName $ParameterName ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` -CallerPSCmdlet $PSCmdlet } @@ -11750,6 +12117,11 @@ function Update-ModuleManifest [System.Reflection.ProcessorArchitecture] $ProcessorArchitecture, + [Parameter()] + [ValidateSet('Desktop','Core')] + [string[]] + $CompatiblePSEditions, + [Parameter()] [ValidateNotNullOrEmpty()] [Version] @@ -11908,20 +12280,27 @@ function Update-ModuleManifest } #Get the original module manifest and migrate all the fields to the new module manifest, including the specified parameter values + $moduleInfo = $null + try { $moduleInfo = Microsoft.PowerShell.Core\Test-ModuleManifest -Path $Path -ErrorAction Stop } catch { - $message = $LocalizedData.TestModuleManifestFail -f ($_.Exception.Message) - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage $message ` - -ErrorId "InvalidModuleManifestFile" ` - -ExceptionObject $Path ` - -CallerPSCmdlet $PSCmdlet ` - -ErrorCategory InvalidArgument - return + # Throw an error only if Test-ModuleManifest did not return the PSModuleInfo object. + # This enables the users to use Update-ModuleManifest cmdlet to update the metadata. + if(-not $moduleInfo) + { + $message = $LocalizedData.TestModuleManifestFail -f ($_.Exception.Message) + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage $message ` + -ErrorId "InvalidModuleManifestFile" ` + -ExceptionObject $Path ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } } #Params to pass to New-ModuleManifest module @@ -12214,6 +12593,31 @@ function Update-ModuleManifest } } + if($CompatiblePSEditions) + { + # CompatiblePSEditions field is not available in PowerShell version lower than 5.1 + # + if (($PSVersionTable.PSVersion -lt [Version]'5.1') -or ($PowerShellVersion -and $PowerShellVersion -lt [Version]'5.1') ` + -or (-not $PowerShellVersion -and $moduleInfo.PowerShellVersion -and $moduleInfo.PowerShellVersion -lt [Version]'5.1') ` + -or (-not $PowerShellVersion -and -not $moduleInfo.PowerShellVersion)) + { + ThrowError -ExceptionName 'System.ArgumentException' ` + -ExceptionMessage $LocalizedData.CompatiblePSEditionsNotSupportedOnLowerPowerShellVersion ` + -ErrorId 'CompatiblePSEditionsNotSupported' ` + -ExceptionObject $CompatiblePSEditions ` + -CallerPSCmdlet $PSCmdlet ` + -ErrorCategory InvalidArgument + return + } + + $params.Add('CompatiblePSEditions', $CompatiblePSEditions) + } + elseif( (Microsoft.PowerShell.Utility\Get-Member -InputObject $moduleInfo -name 'CompatiblePSEditions') -and + $moduleInfo.CompatiblePSEditions) + { + $params.Add('CompatiblePSEditions', $moduleInfo.CompatiblePSEditions) + } + if($HelpInfoUri) { $params.Add("HelpInfoUri",$HelpInfoUri) @@ -12900,7 +13304,13 @@ function Get-ScriptSourceLocation ( [Parameter()] [String] - $Location + $Location, + + [Parameter()] + $Proxy, + + [Parameter()] + $ProxyCredential ) $scriptLocation = $null @@ -12930,6 +13340,8 @@ function Get-ScriptSourceLocation # Ping and resolve the specified location $scriptLocation = Resolve-Location -Location $tempScriptLocation ` -LocationParameterName 'ScriptSourceLocation' ` + -Proxy $Proxy ` + -ProxyCredential $ProxyCredential ` -ErrorAction SilentlyContinue ` -WarningAction SilentlyContinue } @@ -12994,6 +13406,12 @@ function Resolve-Location [Parameter(Mandatory=$true)] [string] $LocationParameterName, + + [Parameter()] + $Proxy, + + [Parameter()] + $ProxyCredential, [Parameter()] [System.Management.Automation.PSCmdlet] @@ -13020,7 +13438,7 @@ function Resolve-Location } else { - $pingResult = Ping-Endpoint -Endpoint $Location + $pingResult = Ping-Endpoint -Endpoint $Location -Proxy $Proxy -ProxyCredential $ProxyCredential $statusCode = $null $exception = $null $resolvedLocation = $null diff --git a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs index aed8e2d4e..8b5153d7e 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs @@ -6,11 +6,13 @@ Copyright (c) Microsoft Corporation. All rights reserved. using System.Collections.ObjectModel; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using System.Text.RegularExpressions; +using Microsoft.PowerShell.Telemetry.Internal; namespace System.Management.Automation { @@ -518,6 +520,9 @@ namespace System.Management.Automation // This is the start of the real implementation of autocomplete/intellisense/tab completion private static CommandCompletion CompleteInputImpl(Ast ast, Token[] tokens, IScriptPosition positionOfCursor, Hashtable options) { + var sw = new Stopwatch(); + sw.Start(); + using (var powershell = PowerShell.Create(RunspaceMode.CurrentRunspace)) { var context = LocalPipeline.GetExecutionContextFromTLS(); @@ -585,8 +590,13 @@ namespace System.Management.Automation } */ } + + var completionResults = results ?? EmptyCompletionResult; + sw.Stop(); + TelemetryAPI.ReportTabCompletionTelemetry(sw.ElapsedMilliseconds, completionResults.Count, + completionResults.Count > 0 ? completionResults[0].ResultType : CompletionResultType.Text); return new CommandCompletion( - new Collection(results ?? EmptyCompletionResult), + new Collection(completionResults), -1, replacementIndex, replacementLength); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index d71495ad6..24ceecc55 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -1376,7 +1376,8 @@ namespace System.Management.Automation var moduleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellModuleFileExtension, - StringLiterals.PowerShellDataFileExtension, + StringLiterals.PowerShellDataFileExtension, + StringLiterals.PowerShellNgenAssemblyExtension, StringLiterals.DependentWorkflowAssemblyExtension, StringLiterals.PowerShellCmdletizationFileExtension, StringLiterals.WorkflowFileExtension diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 9b6b1735a..a391cfbe6 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -3111,8 +3111,9 @@ namespace System.Management.Automation var moduleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellScriptFileExtension, StringLiterals.PowerShellModuleFileExtension, - StringLiterals.PowerShellDataFileExtension, - StringLiterals.DependentWorkflowAssemblyExtension, + StringLiterals.PowerShellDataFileExtension, + StringLiterals.PowerShellNgenAssemblyExtension, + StringLiterals.DependentWorkflowAssemblyExtension, StringLiterals.PowerShellCmdletizationFileExtension, StringLiterals.WorkflowFileExtension }; @@ -4113,7 +4114,7 @@ namespace System.Management.Automation wordToComplete = WildcardPattern.Escape(wordToComplete, Utils.Separators.StarOrQuestion); } - if (!defaultRelative && wordToComplete.Length >= 2 && wordToComplete[1] == ':' && char.IsLetter(wordToComplete[0])) + if (!defaultRelative && wordToComplete.Length >= 2 && wordToComplete[1] == ':' && char.IsLetter(wordToComplete[0]) && context.ExecutionContext != null) { // We don't actually need the drive, but the drive must be "mounted" in PowerShell before completion // can succeed. This call will mount the drive if it wasn't already. diff --git a/src/System.Management.Automation/engine/CommandSearcher.cs b/src/System.Management.Automation/engine/CommandSearcher.cs index ff6620369..5f46c22a1 100644 --- a/src/System.Management.Automation/engine/CommandSearcher.cs +++ b/src/System.Management.Automation/engine/CommandSearcher.cs @@ -607,12 +607,9 @@ namespace System.Management.Automation do // false loop { - if (!File.Exists(path)) + if (!Utils.NativeFileExists(path)) { - CommandDiscovery.discoveryTracer.TraceError( - "The path does not exist: {0}", - path); - + CommandDiscovery.discoveryTracer.TraceError("The path does not exist: {0}", path); break; } diff --git a/src/System.Management.Automation/engine/CompiledCommandParameter.cs b/src/System.Management.Automation/engine/CompiledCommandParameter.cs index fbc237b0b..d973df684 100644 --- a/src/System.Management.Automation/engine/CompiledCommandParameter.cs +++ b/src/System.Management.Automation/engine/CompiledCommandParameter.cs @@ -60,7 +60,7 @@ namespace System.Management.Automation Collection validationAttributes = null; Collection argTransformationAttributes = null; - Collection aliases = null; + string[] aliases = null; // First, process attributes that aren't type conversions foreach (Attribute attribute in runtimeDefinedParameter.Attributes) @@ -166,7 +166,7 @@ namespace System.Management.Automation Collection validationAttributes = null; Collection argTransformationAttributes = null; - Collection aliases = null; + string[] aliases = null; foreach (Attribute attr in memberAttributes) { @@ -179,9 +179,7 @@ namespace System.Management.Automation this.ArgumentTransformationAttributes = argTransformationAttributes == null ? Utils.EmptyArray() : argTransformationAttributes.ToArray(); - this.Aliases = aliases == null - ? Utils.EmptyArray() - : aliases.ToArray(); + this.Aliases = aliases ?? Utils.EmptyArray(); } #endregion ctor @@ -437,7 +435,7 @@ namespace System.Management.Automation Attribute attribute, ref Collection validationAttributes, ref Collection argTransformationAttributes, - ref Collection aliases) + ref string[] aliases) { // NTRAID#Windows Out Of Band Releases-926374-2005/12/22-JonN if (null == attribute) @@ -471,14 +469,15 @@ namespace System.Management.Automation { if (aliases == null) { - aliases = new Collection(aliasAttr.aliasNames); + aliases = aliasAttr.aliasNames; } else { - for (int i = 0; i < aliasAttr.aliasNames.Length; i++) - { - aliases.Add(aliasAttr.aliasNames[i]); - } + var prevAliasNames = aliases; + var newAliasNames = aliasAttr.aliasNames; + aliases = new string[prevAliasNames.Length + newAliasNames.Length]; + Array.Copy(prevAliasNames, aliases, prevAliasNames.Length); + Array.Copy(newAliasNames, 0, aliases, prevAliasNames.Length, newAliasNames.Length); } return; } diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index 5c6c2e35c..1412b6d62 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -2643,9 +2643,10 @@ namespace System.Management.Automation private void OnPSEventReceived(Object sender, PSEventArgs e) { - if (PSEventReceived != null) + PSEventReceivedEventHandler eventHandler = PSEventReceived; + if (eventHandler != null) { - PSEventReceived(sender, e); + eventHandler(sender, e); } } diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index da5ed24d9..885df395a 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.Management.Automation; using System.Diagnostics.CodeAnalysis; @@ -381,6 +382,8 @@ namespace Microsoft.PowerShell.Commands /// protected override void BeginProcessing() { + _timer.Start(); + base.BeginProcessing(); if (ShowCommandInfo.IsPresent && Syntax.IsPresent) @@ -522,8 +525,10 @@ namespace Microsoft.PowerShell.Commands { CommandOrigin origin = this.MyInvocation.CommandOrigin; + int count = 0; foreach (CommandInfo result in results) { + count += 1; // Only write the command if it is visible to the requestor if (SessionState.IsVisible(origin, result)) { @@ -556,6 +561,17 @@ namespace Microsoft.PowerShell.Commands } } } + + _timer.Stop(); + + // We want telemtry on commands people look for but don't exist - this should give us an idea + // what sort of commands people expect but either don't exist, or maybe should be installed by default. + // The StartsWith is to avoid logging telemetry when suggestion mode checks the + // current directory for scripts/exes in the current directory and '.' is not in the path. + if (count == 0 && Name != null && Name.Length > 0 && !Name[0].StartsWith(".\\")) + { + Telemetry.Internal.TelemetryAPI.ReportGetCommandFailed(Name, _timer.ElapsedMilliseconds); + } } /// @@ -1396,6 +1412,8 @@ namespace Microsoft.PowerShell.Commands Collection nounPatterns; Collection _modulePatterns; + private Stopwatch _timer = new Stopwatch(); + #endregion #region ShowCommandInfo support diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index c38707f96..7923ef0de 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -2965,7 +2965,7 @@ namespace System.Management.Automation.Runspaces // Get initial list of public commands in session. HashSet publicCommands = new HashSet(); foreach (CommandInfo sessionCommand in initializedRunspace.ExecutionContext.SessionState.InvokeCommand.GetCommands( - "*", CommandTypes.All, true)) + "*", CommandTypes.Alias | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Cmdlet, true)) { if (sessionCommand.Visibility == SessionStateEntryVisibility.Public) { @@ -3626,15 +3626,12 @@ namespace System.Management.Automation.Runspaces if (this.DefaultCommandVisibility != SessionStateEntryVisibility.Public) { foreach (CommandInfo importedCommand in initializedRunspace.ExecutionContext.SessionState.InvokeCommand.GetCommands( - "*", CommandTypes.All, true)) + "*", CommandTypes.Alias | CommandTypes.Function | CommandTypes.Filter | CommandTypes.Cmdlet, true)) { try { // All commands except for the initial session public commands should be made private. - // Exclude ApplicationInfo/ExternalScriptInfo commands as they throw on the setter (because - // visibility is governed via the ExceutionContext.SessionState lists). - if (!(importedCommand is ApplicationInfo) && !(importedCommand is ExternalScriptInfo) && - (importedCommand.Visibility != this.DefaultCommandVisibility) && + if ((importedCommand.Visibility != this.DefaultCommandVisibility) && !publicCommands.Contains(importedCommand)) { importedCommand.Visibility = this.DefaultCommandVisibility; @@ -6128,6 +6125,7 @@ if($paths) { {"Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) }, {"New-Module", new SessionStateCmdletEntry("New-Module", typeof(NewModuleCommand), helpFile) }, {"New-ModuleManifest", new SessionStateCmdletEntry("New-ModuleManifest", typeof(NewModuleManifestCommand), helpFile) }, + {"New-PSRoleCapabilityFile", new SessionStateCmdletEntry("New-PSRoleCapabilityFile", typeof(NewPSRoleCapabilityFileCommand), helpFile) }, {"New-PSSession", new SessionStateCmdletEntry("New-PSSession", typeof(NewPSSessionCommand), helpFile) }, {"New-PSSessionConfigurationFile", new SessionStateCmdletEntry("New-PSSessionConfigurationFile", typeof(NewPSSessionConfigurationFileCommand), helpFile) }, {"New-PSSessionOption", new SessionStateCmdletEntry("New-PSSessionOption", typeof(NewPSSessionOptionCommand), helpFile) }, @@ -6159,7 +6157,6 @@ if($paths) { {"Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, {"Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) }, {"Get-PSSnapin", new SessionStateCmdletEntry("Get-PSSnapin", typeof(GetPSSnapinCommand), helpFile) }, - {"New-PSRoleCapabilityFile", new SessionStateCmdletEntry("New-PSRoleCapabilityFile", typeof(NewPSRoleCapabilityFileCommand), helpFile) }, {"New-PSTransportOption", new SessionStateCmdletEntry("New-PSTransportOption", typeof(NewPSTransportOptionCommand), helpFile) }, {"Remove-PSSnapin", new SessionStateCmdletEntry("Remove-PSSnapin", typeof(RemovePSSnapinCommand), helpFile) }, {"Resume-Job", new SessionStateCmdletEntry("Resume-Job", typeof(ResumeJobCommand), helpFile) }, @@ -6189,9 +6186,6 @@ if($paths) { } } - internal static readonly CallSite> CreateModuleInitializerInstance = - CallSite>.Create(PSCreateInstanceBinder.Get(new CallInfo(0), null)); - private static void ExecuteModuleInitializer(Assembly assembly, Type[] assemblyTypes, bool isModuleLoad) { for (int i = 0; i < assemblyTypes.Length; i++) @@ -6203,9 +6197,7 @@ if($paths) { if (isModuleLoad && typeof(IModuleAssemblyInitializer).IsAssignableFrom(type) && type != typeof(IModuleAssemblyInitializer)) { _assembliesWithModuleInitializerCache.Value[assembly] = true; - var moduleInitializer = (IModuleAssemblyInitializer) CreateModuleInitializerInstance - .Target.Invoke( - CreateModuleInitializerInstance, type); + IModuleAssemblyInitializer moduleInitializer = (IModuleAssemblyInitializer)Activator.CreateInstance(type, true); moduleInitializer.OnImport(); } } diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 30fa35ddd..aea5ad892 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -24,6 +24,7 @@ using Parser = System.Management.Automation.Language.Parser; using ScriptBlock = System.Management.Automation.ScriptBlock; using Token = System.Management.Automation.Language.Token; using System.Collections.ObjectModel; +using Microsoft.PowerShell.Telemetry.Internal; #if CORECLR // Use stub for SecurityZone. @@ -266,7 +267,7 @@ namespace Microsoft.PowerShell.Commands } set { - if (value == null) + if (string.IsNullOrWhiteSpace(value)) { BaseMaximumVersion = null; } @@ -1752,6 +1753,8 @@ namespace Microsoft.PowerShell.Commands if (null != foundModule) { SetModuleBaseForEngineModules(foundModule.Name, this.Context); + + TelemetryAPI.ReportModuleLoad(foundModule); } } } diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index d8beb019a..ac692127d 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -1256,8 +1256,18 @@ namespace Microsoft.PowerShell.Commands currentlyProcessingModules[file] = null; // Create a fake module info for this file + string extension; + file = file.TrimEnd(); - string extension = Path.GetExtension(file); + // In case the file is a Ngen Assembly. + if (file.EndsWith(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + { + extension = StringLiterals.PowerShellNgenAssemblyExtension; + } + else + { + extension = Path.GetExtension(file); + } ManifestProcessingFlags flags = ManifestProcessingFlags.NullOnFirstError; // We are creating the ModuleInfo for Get-Module..so ignoring @@ -1334,7 +1344,7 @@ namespace Microsoft.PowerShell.Commands } } - else if (extension.Equals(".dll", StringComparison.OrdinalIgnoreCase)) + else if (extension.Equals(StringLiterals.DependentWorkflowAssemblyExtension, StringComparison.OrdinalIgnoreCase) || extension.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase)) { moduleInfo.SetModuleType(ModuleType.Binary); moduleInfo.RootModule = moduleInfo.Path; @@ -2273,12 +2283,18 @@ namespace Microsoft.PowerShell.Commands } else { - string fileName = FixupFileName(moduleBase, assembly, ".dll"); - + string fileName = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellNgenAssemblyExtension); string loadMessage = StringUtil.Format(Modules.LoadingFile, "Assembly", fileName); WriteVerbose(loadMessage); iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); fixedUpAssemblyPathList.Add(fileName); + + fileName = FixupFileName(moduleBase, assembly, StringLiterals.DependentWorkflowAssemblyExtension); + + loadMessage = StringUtil.Format(Modules.LoadingFile, "Assembly", fileName); + WriteVerbose(loadMessage); + iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); + fixedUpAssemblyPathList.Add(fileName); doBind = true; } } @@ -5244,8 +5260,7 @@ namespace Microsoft.PowerShell.Commands { if (typeof(IModuleAssemblyCleanup).IsAssignableFrom(type) && type != typeof(IModuleAssemblyCleanup)) { - var moduleCleanup = (IModuleAssemblyCleanup) PSSnapInHelpers.CreateModuleInitializerInstance.Target.Invoke( - PSSnapInHelpers.CreateModuleInitializerInstance, type); + var moduleCleanup = (IModuleAssemblyCleanup)Activator.CreateInstance(type, true); moduleCleanup.OnRemove(module); } } @@ -5628,8 +5643,10 @@ namespace Microsoft.PowerShell.Commands var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); - foreach (string ext in extensions) + // "ni.dll" has a higher priority then ".dll" to be loaded. + for (int i = 0; i < extensions.Length; i++) { + string ext = extensions[i]; string fileName = fileBaseName + ext; // Get the resolved file name @@ -5859,7 +5876,16 @@ namespace Microsoft.PowerShell.Commands var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); var writingErrors = 0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors); - string ext = Path.GetExtension(fileName); + // In case the file is a Ngen Assembly. + string ext; + if (fileName.EndsWith(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + { + ext = StringLiterals.PowerShellNgenAssemblyExtension; + } + else + { + ext = Path.GetExtension(fileName); + } PSModuleInfo module = null; // If MinimumVersion/RequiredVersion/MaximumVersion has been specified, then only try to process manifest modules... @@ -6113,7 +6139,7 @@ namespace Microsoft.PowerShell.Commands found = false; } } - else if (ext.Equals(".dll", StringComparison.OrdinalIgnoreCase)) + else if (ext.Equals(".dll", StringComparison.OrdinalIgnoreCase) || ext.Equals(StringLiterals.PowerShellNgenAssemblyExtension)) { module = LoadBinaryModule(false, ModuleIntrinsics.GetModuleName(fileName), fileName, null, moduleBase, ss, options, manifestProcessingFlags, prefix, true, true, out found); diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index 6fe29f7e8..129fd8db5 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -479,22 +479,24 @@ namespace System.Management.Automation return new Guid(); } - // The extensions of all of the files that can be processed with Import-Module + // The extensions of all of the files that can be processed with Import-Module, put the ni.dll in front of .dll to have higher priority to be loaded. internal static string[] PSModuleProcessableExtensions = new string[] { StringLiterals.PowerShellDataFileExtension, StringLiterals.PowerShellScriptFileExtension, StringLiterals.PowerShellModuleFileExtension, StringLiterals.PowerShellCmdletizationFileExtension, StringLiterals.WorkflowFileExtension, - ".dll" }; + StringLiterals.PowerShellNgenAssemblyExtension, + StringLiterals.DependentWorkflowAssemblyExtension}; - // A list of the extensions to check for implicit module loading and discovery + // A list of the extensions to check for implicit module loading and discovery, put the ni.dll in front of .dll to have higher priority to be loaded. internal static string[] PSModuleExtensions = new string[] { StringLiterals.PowerShellDataFileExtension, StringLiterals.PowerShellModuleFileExtension, StringLiterals.PowerShellCmdletizationFileExtension, StringLiterals.WorkflowFileExtension, - ".dll" }; + StringLiterals.PowerShellNgenAssemblyExtension, + StringLiterals.DependentWorkflowAssemblyExtension}; /// /// Returns true if the extension is one of the module extensions... @@ -519,7 +521,15 @@ namespace System.Management.Automation internal static string GetModuleName(string path) { string fileName = path == null ? string.Empty : Path.GetFileName(path); - string ext = Path.GetExtension(fileName); + string ext; + if (fileName.EndsWith(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase)) + { + ext = StringLiterals.PowerShellNgenAssemblyExtension; + } + else + { + ext = Path.GetExtension(fileName); + } if (!string.IsNullOrEmpty(ext) && IsPowerShellModuleExtension(ext)) { return fileName.Substring(0, fileName.Length - ext.Length); diff --git a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs index c452b6561..b74c18bfa 100644 --- a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs @@ -141,7 +141,11 @@ namespace Microsoft.PowerShell.Commands { foreach (ModuleSpecification nestedModule in nestedModules) { - if (!IsValidFilePath(nestedModule.Name, module, true) && !IsValidFilePath(nestedModule.Name + ".dll", module, true) && !IsValidFilePath(nestedModule.Name + ".psm1", module, true) && !IsValidGacAssembly(nestedModule.Name)) + if (!IsValidFilePath(nestedModule.Name, module, true) + && !IsValidFilePath(nestedModule.Name + StringLiterals.DependentWorkflowAssemblyExtension, module, true) + && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellNgenAssemblyExtension, module, true) + && !IsValidFilePath(nestedModule.Name + StringLiterals.PowerShellModuleFileExtension, module, true) + && !IsValidGacAssembly(nestedModule.Name)) { // The nested module could be dependencies. We compare if it can be loaded by loadmanifest bool isDependency = false; @@ -323,16 +327,23 @@ namespace Microsoft.PowerShell.Commands { string gacPath = System.Environment.GetEnvironmentVariable("windir") + "\\Microsoft.NET\\assembly"; string assemblyFile = assemblyName; - if (!assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + string ngenAssemblyFile = assemblyName; + if (!assemblyName.EndsWith(StringLiterals.DependentWorkflowAssemblyExtension, StringComparison.OrdinalIgnoreCase)) { - assemblyFile = assemblyName + ".dll"; + assemblyFile = assemblyName + StringLiterals.DependentWorkflowAssemblyExtension; + ngenAssemblyFile = assemblyName + StringLiterals.PowerShellNgenAssemblyExtension; } try { var allFiles = Directory.GetFiles(gacPath, assemblyFile, SearchOption.AllDirectories); + if (allFiles.Length == 0) { - return false; + var allNgenFiles = Directory.GetFiles(gacPath, ngenAssemblyFile, SearchOption.AllDirectories); + if (allNgenFiles.Length == 0) + { + return false; + } } } catch diff --git a/src/System.Management.Automation/engine/SessionStateStrings.cs b/src/System.Management.Automation/engine/SessionStateStrings.cs index e932264be..4ea503ed5 100644 --- a/src/System.Management.Automation/engine/SessionStateStrings.cs +++ b/src/System.Management.Automation/engine/SessionStateStrings.cs @@ -138,6 +138,11 @@ namespace System.Management.Automation /// internal const string DependentWorkflowAssemblyExtension = ".dll"; + /// + /// The file extension (including the dot) of an workflow dependent Ngen assembly + /// + internal const string PowerShellNgenAssemblyExtension = ".ni.dll"; + internal const string PowerShellConsoleFileExtension = ".psc1"; /// diff --git a/src/System.Management.Automation/engine/Types_Ps1Xml.generated.cs b/src/System.Management.Automation/engine/Types_Ps1Xml.generated.cs index 1b16b739f..5f0e7a90f 100644 --- a/src/System.Management.Automation/engine/Types_Ps1Xml.generated.cs +++ b/src/System.Management.Automation/engine/Types_Ps1Xml.generated.cs @@ -689,7 +689,7 @@ namespace System.Management.Automation.Runspaces # WMI team fixed the formatting issue related to InstalledOn # property in Windows7 (to return string)..so returning the WMI's # version directly - [DateTime]::Parse($this.psBase.properties[""InstalledOn""].Value) + [DateTime]::Parse($this.psBase.properties[""InstalledOn""].Value, [System.Globalization.DateTimeFormatInfo]::new()) } else { @@ -1781,7 +1781,7 @@ namespace System.Management.Automation.Runspaces # WMI team fixed the formatting issue related to InstalledOn # property in Windows7 (to return string)..so returning the WMI's # version directly - [DateTime]::Parse($this.psBase.CimInstanceProperties[""InstalledOn""].Value) + [DateTime]::Parse($this.psBase.CimInstanceProperties[""InstalledOn""].Value, [System.Globalization.DateTimeFormatInfo]::new()) } else { diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs index c8dbad660..fb267e465 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Management.Automation.Host; using System.Management.Automation.Internal; +using Microsoft.PowerShell.Telemetry.Internal; using Dbg = System.Management.Automation.Diagnostics; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -308,6 +309,16 @@ namespace System.Management.Automation.Runspaces OpenHelper(syncCall); if (etwEnabled) RunspaceEventSource.Log.OpenRunspaceStop(); + + // We report startup telemtry when opening the runspace - because this is the first time + // we are really using PowerShell. This isn't the cleanest place though, because + // sometimes there are many runspaces created - the callee ensures telemetry is only + // reported once. Note that if the host implements IHostProvidesTelemetryData, we rely + // on the host calling ReportStartupTelemetry. + if (!(this.Host is IHostProvidesTelemetryData)) + { + TelemetryAPI.ReportStartupTelemetry(null); + } } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 093c06e22..c66853dbb 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -16,6 +16,7 @@ using System.Diagnostics.CodeAnalysis; // for fxcop using Dbg = System.Management.Automation.Diagnostics; using System.Diagnostics; using System.Linq; +using Microsoft.PowerShell.Telemetry.Internal; #if CORECLR @@ -933,6 +934,28 @@ namespace System.Management.Automation.Runspaces //Raise Event RaiseRunspaceStateEvents (); + + // Report telemetry if we have no more open runspaces. + bool allRunspacesClosed = true; + bool hostProvidesExitTelemetry = false; + foreach (var r in Runspace.RunspaceList) + { + if (r.RunspaceStateInfo.State != RunspaceState.Closed) + { + allRunspacesClosed = false; + break; + } + var localRunspace = r as LocalRunspace; + if (localRunspace != null && localRunspace.Host is IHostProvidesTelemetryData) + { + hostProvidesExitTelemetry = true; + break; + } + } + if (allRunspacesClosed && !hostProvidesExitTelemetry) + { + TelemetryAPI.ReportExitTelemetry(null); + } } /// diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index 053b69ab2..f64753422 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -1109,6 +1109,8 @@ namespace System.Management.Automation.Host /// public void Dispose() { + if (disposed) { return; } + // Wait for any pending output to be flushed to disk so that Stop-Transcript // can be trusted to immediately have all content from that session in the file) int outputWait = 0; @@ -1124,6 +1126,7 @@ namespace System.Management.Automation.Host { contentWriter.Flush(); contentWriter.Dispose(); + contentWriter = null; } disposed = true; diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs index 5ae32f5c0..70618687d 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs @@ -584,7 +584,7 @@ namespace Microsoft.PowerShell.Commands #region Overrides /// - /// + /// /// protected override void ProcessRecord() { @@ -739,6 +739,8 @@ namespace Microsoft.PowerShell.Commands } else { + DISCUtils.ValidateRoleDefinitions(roleDefinitions); + result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.RoleDefinitions, RemotingErrorIdStrings.DISCRoleDefinitionsComment, SessionConfigurationUtils.CombineHashtable(roleDefinitions, streamWriter), streamWriter, false)); } @@ -1055,6 +1057,10 @@ namespace Microsoft.PowerShell.Commands } } + #endregion + + #region Private methods + private bool ShouldGenerateConfigurationSnippet(string parameterName) { return Full || MyInvocation.BoundParameters.ContainsKey(parameterName); @@ -1068,9 +1074,7 @@ namespace Microsoft.PowerShell.Commands /// /// Creates a role capability file suitable for use in a Role Capability (which can be referenced in a Session Configuration file) /// -#if !CORECLR [Cmdlet(VerbsCommon.New, "PSRoleCapabilityFile", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=623708")] -#endif public class NewPSRoleCapabilityFileCommand : PSCmdlet { #region Parameters @@ -1745,6 +1749,8 @@ namespace Microsoft.PowerShell.Commands #endregion } + #region SessionConfigurationUtils + /// /// Utility methods for configuration file commands /// @@ -2029,4 +2035,6 @@ namespace Microsoft.PowerShell.Commands return sb.ToString(); } } + + #endregion } \ No newline at end of file diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index d6368c285..516591e21 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -2661,7 +2661,7 @@ namespace System.Management.Automation.Runspaces var computeSystemPropertiesHandle = getComputeSystemPropertiesInfo.Invoke(null, new object[]{ ContainerId }); - ContainerName = (string)computeSystemPropertiesType.GetProperty("Name").GetValue(computeSystemPropertiesHandle); + ContainerName = (string)computeSystemPropertiesType.GetProperty("SiloGuid").GetValue(computeSystemPropertiesHandle); RuntimeId = (Guid)computeSystemPropertiesType.GetProperty("RuntimeId").GetValue(computeSystemPropertiesHandle); } catch (FileNotFoundException) diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 886483780..650d1618e 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -947,12 +947,6 @@ namespace System.Management.Automation.Remoting internal static readonly string VisibleProviders = "VisibleProviders"; internal static readonly string VisibleExternalCommands = "VisibleExternalCommands"; - // Keys that are not allowed in RoleDefinitions or a role capability - internal static HashSet DisallowedRoleCapabilityKeys = new HashSet(StringComparer.OrdinalIgnoreCase) - { - PowerShellVersion, SessionType, RoleDefinitions, LanguageMode, ExecutionPolicy - }; - internal static ConfigTypeEntry[] ConfigFileKeys = new ConfigTypeEntry[] { new ConfigTypeEntry(AliasDefinitions, new ConfigTypeEntry.TypeValidationCallback(AliasDefinitionsTypeValidationCallback)), new ConfigTypeEntry(AssembliesToLoad, new ConfigTypeEntry.TypeValidationCallback(StringArrayTypeValidationCallback)), @@ -1366,13 +1360,41 @@ namespace System.Management.Automation.Remoting } } + #region DISC Utilities + /// /// DISC utilities /// internal static class DISCUtils { + #region Private data + internal static Type ExecutionPolicyType = null; + /// + /// !! NOTE that this list MUST be updated when new capability session configuration properties are added. + /// + private static readonly HashSet AllowedRoleCapabilityKeys = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "RoleCapabilities", + "ModulesToImport", + "VisibleAliases", + "VisibleCmdlets", + "VisibleFunctions", + "VisibleExternalCommands", + "VisibleProviders", + "ScriptsToProcess", + "AliasDefinitions", + "FunctionDefinitions", + "VariableDefinitions", + "EnvironmentVariables", + "TypesToProcess", + "FormatsToProcess", + "AssembliesToLoad" + }; + + #endregion + /// /// Create an ExternalScriptInfo object from a file path. /// @@ -1432,8 +1454,6 @@ namespace System.Management.Automation.Remoting return result as Hashtable; } - - /// /// Verifies the configuration hashtable /// @@ -1606,8 +1626,68 @@ namespace System.Management.Automation.Remoting } } } + + /// + /// Validates Role Definition hash entries + /// + /// RoleDefinitions = @{ + /// 'Everyone' = @{ + /// 'RoIeCapabilities' = 'Basic' }; + /// 'Administrators' = @{ + /// 'VisibleCmdlets' = 'Get-Process','Get-Location'; 'VisibleFunctions = 'TabExpansion2' } } + /// + /// + internal static void ValidateRoleDefinitions(IDictionary roleDefinitions) + { + foreach (var roleKey in roleDefinitions.Keys) + { + if (!(roleKey is string)) + { + var invalidOperationEx = new PSInvalidOperationException( + string.Format(RemotingErrorIdStrings.InvalidRoleKeyType, roleKey.GetType().FullName)); + invalidOperationEx.SetErrorId("InvalidRoleKeyType"); + throw invalidOperationEx; + } + + // + // Each role capability in the role definition item should contain a hash table with allowed role capability key. + // + + IDictionary roleDefinition = roleDefinitions[roleKey] as IDictionary; + if (roleDefinition == null) + { + var invalidOperationEx = new PSInvalidOperationException( + StringUtil.Format(RemotingErrorIdStrings.InvalidRoleValue, roleKey)); + invalidOperationEx.SetErrorId("InvalidRoleEntryNotHashtable"); + throw invalidOperationEx; + } + + foreach (var key in roleDefinition.Keys) + { + // Ensure each role capability key is valid. + string roleCapabilityKey = key as string; + if (roleCapabilityKey == null) + { + var invalidOperationEx = new PSInvalidOperationException( + string.Format(RemotingErrorIdStrings.InvalidRoleCapabilityKeyType, key.GetType().FullName)); + invalidOperationEx.SetErrorId("InvalidRoleCapabilityKeyType"); + throw invalidOperationEx; + } + + if (!AllowedRoleCapabilityKeys.Contains(roleCapabilityKey)) + { + var invalidOperationEx = new PSInvalidOperationException( + string.Format(RemotingErrorIdStrings.InvalidRoleCapabilityKey, roleCapabilityKey)); + invalidOperationEx.SetErrorId("InvalidRoleCapabilityKey"); + throw invalidOperationEx; + } + } + } + } } + #endregion + /// /// Creates an initial session state based on the configuration language for PSSC files /// @@ -1683,12 +1763,15 @@ namespace System.Management.Automation.Remoting { string message = StringUtil.Format(RemotingErrorIdStrings.InvalidRoleEntry, configHash["Roles"].GetType().FullName); PSInvalidOperationException ioe = new PSInvalidOperationException(message); - ioe.SetErrorId("InvalidRoleEntryNotHashtable"); + ioe.SetErrorId("InvalidRoleDefinitionNotHashtable"); throw ioe; } + // Ensure that role definitions contain valid entries. + DISCUtils.ValidateRoleDefinitions(roleEntry); + // Go through the Roles hashtable - foreach(Object role in roleEntry.Keys) + foreach (Object role in roleEntry.Keys) { // Check if this role applies to the connected user if (roleVerifier(role.ToString())) @@ -1744,13 +1827,6 @@ namespace System.Management.Automation.Remoting foreach (Object customization in childConfigHash.Keys) { string customizationString = customization.ToString(); - if (ConfigFileConstants.DisallowedRoleCapabilityKeys.Contains(customizationString)) - { - string message = StringUtil.Format(RemotingErrorIdStrings.InvalidRoleCapabilityKey, customizationString); - PSInvalidOperationException ioe = new PSInvalidOperationException(message); - ioe.SetErrorId("InvalidRoleTookitKey"); - throw ioe; - } ArrayList customizationValue = new ArrayList(); diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs index de01f477f..251db9086 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs @@ -323,11 +323,14 @@ namespace System.Management.Automation.Remoting { if (_serverDriverRemoteHost.PropagatePop) { - // Optionally forward the PopRunspace command to client. + // Forward the PopRunspace command to client and keep *this* pushed runspace as + // the configured JEA restricted session. _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.PopRunspace); } - - _serverDriverRemoteHost.PopRunspace(); + else + { + _serverDriverRemoteHost.PopRunspace(); + } } else { diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index ca48ef6f2..9ced86889 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -515,8 +515,6 @@ namespace System.Management.Automation // Let exceptions propagate. RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(this.configurationName, this.remoteHost); - // Ensure session ends and configured runspace is closed on pop. - remoteRunspace.ShouldCloseOnPop = true; this.remoteHost.AllowPushRunspace = true; this.remoteHost.PropagatePop = true; @@ -665,7 +663,9 @@ namespace System.Management.Automation args.Runspace.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - // Set the current location to Personal folder for this runspace. + // Set the current location to MyDocuments folder for this runspace. + // This used to be set to the Personal folder but was changed to MyDocuments folder for + // compatibility with PowerShell on Nano Server for PowerShell V5. // This is needed because in the remoting scenario, Environment.CurrentDirectory // always points to System Folder (%windir%\system32) irrespective of the // user as %HOMEDRIVE% and %HOMEPATH% are not available for the logon process. @@ -676,17 +676,12 @@ namespace System.Management.Automation string personalfolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); args.Runspace.ExecutionContext.EngineSessionState.SetLocation(personalfolder); } - catch (ArgumentException) - { - } - catch (ProviderNotFoundException) - { - } - catch (DriveNotFoundException) - { - } - catch (ProviderInvocationException) + catch (Exception e) { + // SetLocation API can call 3rd party code and so there is no telling what exception may be thrown. + // Setting location is not critical and is expected not to work with some account types, so we want + // to ignore all but critical errors. + CommandProcessorBase.CheckForSevereException(e); } // Run startup scripts @@ -875,6 +870,7 @@ namespace System.Management.Automation StartPowerShellCommandOnPushedRunspace( powershell, + null, data.PowerShellId, data.RunspacePoolId, hostInfo, @@ -1085,23 +1081,41 @@ namespace System.Management.Automation "Name", "Namespace", "HelpUri", "CommandType", "ResolvedCommandName", "OutputType", "Parameters" }); HostInfo useRunspaceHost = new HostInfo(null); - useRunspaceHost.UseRunspaceHost = true; - ServerPowerShellDriver driver = new ServerPowerShellDriver( - countingPipeline, - mainPipeline, - true /* no input */, - data.PowerShellId, - data.RunspacePoolId, - this, -#if !CORECLR // No ApartmentState In CoreCLR - ApartmentState.Unknown, -#endif - useRunspaceHost, - 0 /* stream options */, - false /* addToHistory */, - null /* use default rsPool runspace */); + useRunspaceHost.UseRunspaceHost = true; - driver.Start(); + if (remoteHost.IsRunspacePushed) + { + // If we have a pushed runspace then execute there. + StartPowerShellCommandOnPushedRunspace( + countingPipeline, + mainPipeline, + data.PowerShellId, + data.RunspacePoolId, + useRunspaceHost, + 0, + true, + false); + } + else + { + // Run on usual driver. + ServerPowerShellDriver driver = new ServerPowerShellDriver( + countingPipeline, + mainPipeline, + true /* no input */, + data.PowerShellId, + data.RunspacePoolId, + this, +#if !CORECLR // No ApartmentState In CoreCLR + ApartmentState.Unknown, +#endif + useRunspaceHost, + 0 /* stream options */, + false /* addToHistory */, + null /* use default rsPool runspace */); + + driver.Start(); + } } /// @@ -1206,6 +1220,7 @@ namespace System.Management.Automation /// Starts the PowerShell command on the currently pushed Runspace /// /// PowerShell command or script + /// PowerShell command to run after first completes /// PowerShell Id /// RunspacePool Id /// Host Info @@ -1214,6 +1229,7 @@ namespace System.Management.Automation /// Add to history private void StartPowerShellCommandOnPushedRunspace( PowerShell powershell, + PowerShell extraPowerShell, Guid powershellId, Guid runspacePoolId, HostInfo hostInfo, @@ -1225,7 +1241,7 @@ namespace System.Management.Automation ServerPowerShellDriver driver = new ServerPowerShellDriver( powershell, - null, + extraPowerShell, noInput, powershellId, runspacePoolId, diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index d2e5b8955..7fd08b9ec 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -1053,29 +1053,41 @@ namespace System.Management.Automation.Remoting if (onConnect) { - // Win10 server can support Win8 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) + bool connectSupported = false; + + // Win10 server can support reconstruct/reconnect for all 2.x protocol versions + // that support reconstruct/reconnect, Protocol 2.2+ + // Major protocol version differences (2.x -> 3.x) are not supported. + if ((serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) && + (clientProtocolVersion.Major == serverProtocolVersion.Major)) { - // - report that server is Win8 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; - _context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - else - { - // All other version mismatches, throw an error. - if (clientProtocolVersion != serverProtocolVersion) + if (clientProtocolVersion.Minor == RemotingConstants.ProtocolVersionWin8RTM.Minor) { - PSRemotingDataStructureException reasonOfFailure = - new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnNegotiation, - RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME, - clientProtocolVersion, - PSVersionInfo.BuildVersion, - RemotingConstants.ProtocolVersion); - throw reasonOfFailure; + // Report that server is Win8 version to the client + // Protocol: 2.2 + connectSupported = true; + serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; + _context.ServerCapability.ProtocolVersion = serverProtocolVersion; } + else if (clientProtocolVersion.Minor > RemotingConstants.ProtocolVersionWin8RTM.Minor) + { + // All other minor versions are supported and the server returns its full capability + // Protocol: 2.3, 2.4, 2.5 ... + connectSupported = true; + } + } + + if (!connectSupported) + { + // Throw for protocol versions 2.x that don't support disconnect/reconnect. + // Protocol: < 2.2 + PSRemotingDataStructureException reasonOfFailure = + new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnNegotiation, + RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME, + clientProtocolVersion, + PSVersionInfo.BuildVersion, + RemotingConstants.ProtocolVersion); + throw reasonOfFailure; } } else diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 71492531a..6bec3a28f 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -22,6 +22,7 @@ using System.Text; using Microsoft.Win32; using System.Threading; using System.Threading.Tasks; +using Microsoft.PowerShell.Telemetry.Internal; #if CORECLR // Use stub for Serializable attribute and ISerializable related types @@ -63,6 +64,8 @@ namespace System.Management.Automation internal bool Compile(bool optimized) { + var sw = new Stopwatch(); + sw.Start(); if (_attributes == null) { InitializeMetadata(); @@ -86,6 +89,9 @@ namespace System.Management.Automation CompileOptimized(); } + sw.Stop(); + TelemetryAPI.ReportScriptTelemetry((Ast)_ast, !optimized, sw.ElapsedMilliseconds); + return optimized; } diff --git a/src/System.Management.Automation/help/HelpCommands.cs b/src/System.Management.Automation/help/HelpCommands.cs index 49d08cb04..ed8593d90 100644 --- a/src/System.Management.Automation/help/HelpCommands.cs +++ b/src/System.Management.Automation/help/HelpCommands.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Management.Automation.Runspaces; using System.Collections.ObjectModel; using System.Collections.Generic; +using System.Diagnostics; using System.Management.Automation; using System.Globalization; using System.Management.Automation.Internal; @@ -306,6 +307,9 @@ namespace Microsoft.PowerShell.Commands private GraphicalHostReflectionWrapper graphicalHostReflectionWrapper; #endif + private readonly Stopwatch _timer = new Stopwatch(); + private bool _updatedHelp; + #endregion #region Cmdlet API implementation @@ -315,11 +319,14 @@ namespace Microsoft.PowerShell.Commands /// protected override void BeginProcessing() { + _timer.Start(); + if (!Online.IsPresent && UpdatableHelpSystem.ShouldPromptToUpdateHelp() && HostUtilities.IsProcessInteractive(MyInvocation) && HasInternetConnection()) { if(ShouldContinue(HelpDisplayStrings.UpdateHelpPromptBody, HelpDisplayStrings.UpdateHelpPromptTitle)) { System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace).AddCommand("Update-Help").Invoke(); + _updatedHelp = true; } UpdatableHelpSystem.SetDisablePromptToUpdateHelp(); @@ -397,6 +404,11 @@ namespace Microsoft.PowerShell.Commands countOfHelpInfos++; } + _timer.Stop(); + + if (!string.IsNullOrEmpty(Name)) + Microsoft.PowerShell.Telemetry.Internal.TelemetryAPI.ReportGetHelpTelemetry(Name, countOfHelpInfos, _timer.ElapsedMilliseconds, _updatedHelp); + // Write full help as there is only one help info object if (1 == countOfHelpInfos) { diff --git a/src/System.Management.Automation/help/HelpCommentsParser.cs b/src/System.Management.Automation/help/HelpCommentsParser.cs index e4a2e4f3e..4cc9e18d9 100644 --- a/src/System.Management.Automation/help/HelpCommentsParser.cs +++ b/src/System.Management.Automation/help/HelpCommentsParser.cs @@ -172,7 +172,7 @@ namespace System.Management.Automation XmlElement parameterValue = doc.CreateElement("command:parameterValue", commandURI); parameterValue.SetAttribute("required", isSwitchParameter ? "false" : "true"); //parameterValue.SetAttribute("variableLength", "unknown"); - XmlText parameterValue_text = doc.CreateTextNode(elementType.Name); + XmlText parameterValue_text = doc.CreateTextNode(type.Name); command_parameter.AppendChild(parameterValue).AppendChild(parameterValue_text); } } diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index 74aaef459..452090122 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -1071,9 +1071,6 @@ All WinRM sessions connected to Windows PowerShell session configurations, such The 'Roles' entry must be a hashtable, but was a {0}. - - The key '{0}' is not valid in a role capability or role definition. - Could not convert the value of the '{0}' role entry to a hashtable. The 'Roles' entry must be a hashtable with group names for keys, where the value associated with each key is another hashtable of session configuration properties for that role. @@ -1599,4 +1596,13 @@ All WinRM sessions connected to Windows PowerShell session configurations, such Cannot create a session User Drive because the current user name contains invalid file path characters. + + Invalid role capability key: {0}. Make sure the role capability name is spelled correctly and is a valid session configuration property. + + + Invalid role capability key type: {0}. Role capability keys must be strings that identify a valid session configuration property. + + + Invalid role key type: {0}. Role keys must be strings that identify a security group. + \ No newline at end of file diff --git a/src/System.Management.Automation/security/nativeMethods.cs b/src/System.Management.Automation/security/nativeMethods.cs index 27c769369..b04c6ef5e 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -2144,8 +2144,44 @@ namespace System.Management.Automation.Security [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] internal static extern IntPtr CryptCATStoreFromHandle( IntPtr hCatalog - ); - + ); + + [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminAcquireContext2( + ref IntPtr phCatAdmin, + IntPtr pgSubsystem, + [MarshalAs(UnmanagedType.LPWStr)] + string pwszHashAlgorithm, + IntPtr pStrongHashPolicy, + DWORD dwFlags + ); + + [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminReleaseContext( + IntPtr phCatAdmin, + DWORD dwFlags + ); + + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern unsafe IntPtr CreateFile( + string lpFileName, + DWORD dwDesiredAccess, + DWORD dwShareMode, + DWORD lpSecurityAttributes, + DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, + IntPtr hTemplateFile + ); + + [DllImport("wintrust.dll", SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern bool CryptCATAdminCalcHashFromFileHandle2( + IntPtr hCatAdmin, + IntPtr hFile, + [In, Out] ref DWORD pcbHash, + IntPtr pbHash, + DWORD dwFlags + ); + [DllImport("wintrust.dll", CharSet = CharSet.Unicode)] internal static extern IntPtr CryptCATEnumerateMember( IntPtr hCatalog, diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index 9f6d54e0e..edb4de9d6 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -479,6 +479,7 @@ namespace System.Management.Automation { uint zoneId; object curSecMgr = null; + const UInt32 MUTZ_DONT_USE_CACHE = 0x00001000; int hr = NativeMethods.CoInternetCreateSecurityManager(null, out curSecMgr, 0); if (hr != NativeMethods.S_OK) @@ -490,7 +491,7 @@ namespace System.Management.Automation try { NativeMethods.IInternetSecurityManager ism = (NativeMethods.IInternetSecurityManager)curSecMgr; - hr = ism.MapUrlToZone(filePath, out zoneId, 0); + hr = ism.MapUrlToZone(filePath, out zoneId, MUTZ_DONT_USE_CACHE); if (hr == NativeMethods.S_OK) { SecurityZone result; diff --git a/src/System.Management.Automation/utils/PSTelemetryMethods.cs b/src/System.Management.Automation/utils/PSTelemetryMethods.cs index 9d7eac52c..3aacd6083 100644 --- a/src/System.Management.Automation/utils/PSTelemetryMethods.cs +++ b/src/System.Management.Automation/utils/PSTelemetryMethods.cs @@ -3,10 +3,21 @@ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; using System.Management.Automation.Internal; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Language; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerShell.Telemetry.Internal; +#if CORECLR +using Environment = System.Management.Automation.Environment; +#else +using Environment = System.Environment; +#endif namespace Microsoft.PowerShell.Telemetry.Internal { @@ -15,7 +26,7 @@ namespace Microsoft.PowerShell.Telemetry.Internal [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] public static class TelemetryAPI { - #region Public API +#region Public API /// /// Public API to expose Telemetry in PowerShell @@ -27,6 +38,369 @@ namespace Microsoft.PowerShell.Telemetry.Internal TelemetryWrapper.TraceMessage(message, arguments); } - #endregion +#endregion + + static int anyPowerShellSessionOpen; + static DateTime sessionStartTime; + + enum HostIsInteractive + { + Unknown, + Iteractive, + NonInteractive + } + + /// + /// Called either after opening a runspace (the default), or by the host application. + /// + public static void ReportStartupTelemetry(IHostProvidesTelemetryData ihptd) + { + // Avoid reporting startup more than once, except if we report "exited" and + // another runspace gets opened. + if (Interlocked.CompareExchange(ref anyPowerShellSessionOpen, 1, 0) == 1) + return; + + bool is32Bit; +#if CORECLR + is32Bit = IntPtr.Size == 4; +#else + is32Bit = !Environment.Is64BitProcess; +#endif + var psversion = PSVersionInfo.PSVersion.ToString(); + var hostName = Process.GetCurrentProcess().ProcessName; + if (ihptd != null) + { + TelemetryWrapper.TraceMessage("PSHostStart", new + { + Interactive = ihptd.HostIsInteractive ? HostIsInteractive.Iteractive : HostIsInteractive.NonInteractive, + ProfileLoadTime = ihptd.ProfileLoadTimeInMS, + ReadyForInputTime = ihptd.ReadyForInputTimeInMS, + Is32Bit = is32Bit, + PSVersion = psversion, + ProcessName = hostName, + }); + } + else + { + TelemetryWrapper.TraceMessage("PSHostStart", new + { + Interactive = HostIsInteractive.Unknown, + ProfileLoadTime = 0, + ReadyForInputTime = 0, + Is32Bit = is32Bit, + PSVersion = psversion, + ProcessName = hostName, + }); + } + sessionStartTime = DateTime.Now; + } + + /// + /// Called after there are no more open runspaces. In some host applications, this could + /// report multiple exits. + /// + public static void ReportExitTelemetry(IHostProvidesTelemetryData ihptd) + { + TelemetryWrapper.TraceMessage("PSHostStop", new + { + InteractiveCommandCount = ihptd != null ? ihptd.InteractiveCommandCount : 0, + TabCompletionTimes = tabCompletionTimes, + TabCompletionCounts = tabCompletionCounts, + TabCompletionResultCounts = tabCompletionResultCounts, + SessionTime = (DateTime.Now - sessionStartTime).TotalMilliseconds + }); + + // In case a host opens another runspace, we will want another PSHostStart event, + // so reset our flag here to allow that event to fire. + anyPowerShellSessionOpen = 0; + } + + /// + /// Report Get-Help requests, how many results are returned, and how long it took. + /// + internal static void ReportGetHelpTelemetry(string name, int topicsFound, long timeInMS, bool updatedHelp) + { + TelemetryWrapper.TraceMessage("PSHelpRequest", new + { + TopicCount = topicsFound, + TimeInMS = timeInMS, + RanUpdateHelp = updatedHelp, + HelpTopic = name, + }); + } + + /// + /// Report when Get-Command fails to find something. + /// + internal static void ReportGetCommandFailed(string[] name, long timeInMS) + { + TelemetryWrapper.TraceMessage("PSGetCommandFailed", new { TimeInMS = timeInMS, CommandNames = name }); + } + + + private static long[] tabCompletionTimes = new long[(int)CompletionResultType.DynamicKeyword + 1]; + private static int[] tabCompletionCounts = new int[(int)CompletionResultType.DynamicKeyword + 1]; + private static int[] tabCompletionResultCounts = new int[(int)CompletionResultType.DynamicKeyword + 1]; + internal static void ReportTabCompletionTelemetry(long elapsedMilliseconds, int count, CompletionResultType completionResultType) + { + // We'll collect some general statistics. + int idx = (int)completionResultType; + if (idx >= 0 && idx <= (int)CompletionResultType.DynamicKeyword) + { + tabCompletionTimes[idx] += elapsedMilliseconds; + tabCompletionCounts[idx]++; + tabCompletionResultCounts[idx] += count; + } + + // Also write an event for any slow tab completion (> 250ms). + if (elapsedMilliseconds > 250) + { + TelemetryWrapper.TraceMessage("PSSlowTabCompletion", new { + Time = elapsedMilliseconds, + Count = count, + Type = completionResultType, + }); + } + } + + /// + /// Report that a module was loaded, but only do so for modules that *might* be authored by Microsoft. We can't + /// be 100% certain, but we'll ignore non-Microsoft module names when looking at any data, so it's best to + /// at least attempt avoiding collecting data we'll ignore. + /// + internal static void ReportModuleLoad(PSModuleInfo foundModule) + { + var modulePath = foundModule.Path; + var companyName = foundModule.CompanyName; + bool couldBeMicrosoftModule = + (modulePath != null && + (modulePath.StartsWith(Utils.GetApplicationBase(Utils.DefaultPowerShellShellID), StringComparison.OrdinalIgnoreCase) || + // The following covers both 64 and 32 bit Program Files by assuming 32bit is just ...\Program Files + " (x86)" + modulePath.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), StringComparison.OrdinalIgnoreCase))) || + (companyName != null && + foundModule.CompanyName.StartsWith("Microsoft", StringComparison.OrdinalIgnoreCase)); + if (couldBeMicrosoftModule) + { + TelemetryWrapper.TraceMessage("PSImportModule", new + { + ModuleName = foundModule.Name, + Version = foundModule.Version.ToString() + }); + } + } + + enum ScriptFileType + { + None = 0, + Ps1 = 1, + Psd1 = 2, + Psm1 = 3, + Other = 4, + } + + private static readonly int promptHashCode = "prompt".GetHashCode(); + + /// + /// Report some telemetry about the scripts that are run + /// + internal static void ReportScriptTelemetry(Ast ast, bool dotSourced, long compileTimeInMS) + { + if (ast.Parent != null || !TelemetryWrapper.IsEnabled) + return; + + Task.Run(() => + { + var extent = ast.Extent; + var text = extent.Text; + var hash = text.GetHashCode(); + + // Ignore 'prompt' so we don't generate an event for every 'prompt' that is invoked. + // (We really should only create 'prompt' once, but we don't. + if (hash == promptHashCode) + return; + + var visitor = new ScriptBlockTelemetry(); + + ast.Visit(visitor); + + var scriptFileType = ScriptFileType.None; + var fileName = extent.File; + if (fileName != null) + { + var ext = System.IO.Path.GetExtension(fileName); + if (".ps1".Equals(ext, StringComparison.OrdinalIgnoreCase)) + { + scriptFileType = ScriptFileType.Ps1; + } + else if (".psd1".Equals(ext, StringComparison.OrdinalIgnoreCase)) + { + scriptFileType = ScriptFileType.Psd1; + } + else if (".psm1".Equals(ext, StringComparison.OrdinalIgnoreCase)) + { + scriptFileType = ScriptFileType.Psm1; + } + else + { + // Reachable? + scriptFileType = ScriptFileType.Other; + } + } + + TelemetryWrapper.TraceMessage("PSScriptDetails", new + { + Hash = hash, + IsDotSourced = dotSourced, + ScriptFileType = scriptFileType, + Length = text.Length, + LineCount = extent.EndLineNumber - extent.StartLineNumber, + CompileTimeInMS = compileTimeInMS, + StatementCount = visitor.StatementCount, + CountOfCommands = visitor.CountOfCommands, + CountOfDotSourcedCommands = visitor.CountOfDotSourcedCommands, + MaxArrayLength = visitor.MaxArraySize, + ArrayLiteralCount = visitor.ArrayLiteralCount, + ArrayLiteralCumulativeSize = visitor.ArrayLiteralCumulativeSize, + MaxStringLength = visitor.MaxStringSize, + StringLiteralCount = visitor.StringLiteralCount, + StringLiteralCumulativeSize = visitor.StringLiteralCumulativeSize, + MaxPipelineDepth = visitor.MaxPipelineDepth, + PipelineCount = visitor.PipelineCount, + FunctionCount = visitor.FunctionCount, + ScriptBlockCount = visitor.ScriptBlockCount, + ClassCount = visitor.ClassCount, + EnumCount = visitor.EnumCount, + CommandsCalled = visitor.CommandsCalled, + }); + }); + } } -} \ No newline at end of file + + class ScriptBlockTelemetry : AstVisitor2 + { + internal ScriptBlockTelemetry() + { + CommandsCalled = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + internal Dictionary CommandsCalled { get; private set; } + internal int CountOfCommands { get; private set; } + internal int CountOfDotSourcedCommands { get; private set; } + + public override AstVisitAction VisitCommand(CommandAst commandAst) + { + CountOfCommands++; + var commandName = commandAst.GetCommandName(); + if (commandName != null) + { + int commandCount; + CommandsCalled.TryGetValue(commandName, out commandCount); + CommandsCalled[commandName] = commandCount + 1; + } + + if (commandAst.InvocationOperator == TokenKind.Dot) + CountOfDotSourcedCommands++; + + return AstVisitAction.Continue; + } + + internal int MaxStringSize { get; private set; } + internal int StringLiteralCount { get; private set; } + internal int StringLiteralCumulativeSize { get; private set; } + public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) + { + var stringSize = stringConstantExpressionAst.Value.Length; + StringLiteralCount += 1; + StringLiteralCumulativeSize += stringSize; + MaxStringSize = Math.Max(MaxStringSize, stringSize); + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) + { + var stringSize = expandableStringExpressionAst.Value.Length; + StringLiteralCount += 1; + StringLiteralCumulativeSize += stringSize; + MaxStringSize = Math.Max(MaxStringSize, stringSize); + return AstVisitAction.Continue; + } + + internal int MaxArraySize { get; private set; } + internal int ArrayLiteralCount { get; private set; } + internal int ArrayLiteralCumulativeSize { get; private set; } + public override AstVisitAction VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) + { + var elementCount = arrayLiteralAst.Elements.Count; + ArrayLiteralCount += 1; + ArrayLiteralCumulativeSize += elementCount; + MaxArraySize = Math.Max(MaxArraySize, elementCount); + return AstVisitAction.Continue; + } + + internal int StatementCount { get; private set; } + public override AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst) + { + StatementCount += blockStatementAst.Body.Statements.Count; + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) + { + StatementCount += namedBlockAst.Statements.Count; + return AstVisitAction.Continue; + } + + internal int FunctionCount { get; private set; } + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + FunctionCount += 1; + return AstVisitAction.Continue; + } + + internal int ScriptBlockCount { get; private set; } + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + ScriptBlockCount += 1; + return AstVisitAction.Continue; + } + + internal int MaxPipelineDepth { get; private set; } + internal int PipelineCount { get; private set; } + public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) + { + MaxPipelineDepth = Math.Max(MaxPipelineDepth, pipelineAst.PipelineElements.Count); + PipelineCount += 1; + return AstVisitAction.Continue; + } + + internal int ClassCount { get; private set; } + internal int EnumCount { get; private set; } + public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) + { + if (typeDefinitionAst.IsClass) + ClassCount += 1; + else if (typeDefinitionAst.IsEnum) + EnumCount += 1; + return AstVisitAction.Continue; + } + } + + /// + /// If implemented by the host, the host should call and + /// and track the data defined by this interface. + /// + public interface IHostProvidesTelemetryData + { + /// A host sets this property as appropriate - used when reporting telemetry. + bool HostIsInteractive { get; } + + /// A host sets this property as appropriate - used when reporting telemetry. + double ProfileLoadTimeInMS { get; } + + /// A host sets this property as appropriate - used when reporting telemetry. + double ReadyForInputTimeInMS { get; } + + /// A host sets this property as appropriate - used when reporting telemetry. + int InteractiveCommandCount { get; } + } +} diff --git a/src/System.Management.Automation/utils/PSTelemetryWrapper.cs b/src/System.Management.Automation/utils/PSTelemetryWrapper.cs index 38cc18589..74ecb2ad9 100644 --- a/src/System.Management.Automation/utils/PSTelemetryWrapper.cs +++ b/src/System.Management.Automation/utils/PSTelemetryWrapper.cs @@ -44,10 +44,10 @@ namespace System.Management.Automation.Internal const int EtwSelfDescribingEventFormat = 8; var eventSourceSettingsEnumObject = Enum.ToObject(eventSourceSettingsType, EtwSelfDescribingEventFormat); - // Create instance of the class EventSource with Provider name of "Microsoft.Windows.PowerShell" + // Create instance of the class EventSource with Provider name of "Microsoft-PowerShell-Telemetry" // Supply this eventSourceTrait to EventSource constructor to enable Asimov type events var eventSource = (EventSource)Activator.CreateInstance(typeof(EventSource), - new object[] {"Microsoft.Windows.PowerShell", + new object[] {"Microsoft-PowerShell-Telemetry", eventSourceSettingsEnumObject, new[] { "ETW_GROUP", "{4f50731a-89cf-4782-b3e0-dce8c90476ba}" }});