diff --git a/src/System.Management.Automation/engine/MshCmdlet.cs b/src/System.Management.Automation/engine/MshCmdlet.cs index f421923527d..12979eaf956 100644 --- a/src/System.Management.Automation/engine/MshCmdlet.cs +++ b/src/System.Management.Automation/engine/MshCmdlet.cs @@ -356,6 +356,11 @@ public CommandInfo GetCommand(string commandName, CommandTypes type, object[] ar /// public System.EventHandler PostCommandLookupAction { get; set; } + /// + /// Gets or sets the action that is invoked everytime the runspace location (cwd) is changed. + /// + public System.EventHandler LocationChangedAction { get; set; } + /// /// Returns the CmdletInfo object that corresponds to the name argument /// diff --git a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs index 479186d9c62..2112f57767f 100644 --- a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs @@ -200,6 +200,7 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) throw PSTraceSource.NewArgumentNullException("path"); } + PathInfo current = CurrentLocation; string originalPath = path; string driveName = null; ProviderInfo provider = null; @@ -553,6 +554,15 @@ internal PathInfo SetLocation(string path, CmdletProviderContext context) // Set the $PWD variable to the new location this.SetVariable(SpecialVariables.PWDVarPath, this.CurrentLocation, false, true, CommandOrigin.Internal); + + // If an action has been defined for location changes, invoke it now. + if (PublicSessionState.InvokeCommand.LocationChangedAction != null) + { + var eventArgs = new LocationChangedEventArgs(PublicSessionState, current, CurrentLocation); + PublicSessionState.InvokeCommand.LocationChangedAction.Invoke(ExecutionContext.CurrentRunspace, eventArgs); + s_tracer.WriteLine("Invoked LocationChangedAction"); + } + return this.CurrentLocation; } // SetLocation @@ -1046,5 +1056,47 @@ internal PathInfoStack SetDefaultLocationStack(string stackName) #endregion push-Pop current working directory } // SessionStateInternal class + + /// + /// Event argument for the LocationChangedAction containing + /// information about the old location we were in and the new + /// location we changed to. + /// + public class LocationChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the LocationChangedEventArgs class. + /// + /// + /// The public session state instance associated with this runspace. + /// + /// + /// The path we changed locations from. + /// + /// + /// The path we change locations to. + /// + internal LocationChangedEventArgs(SessionState sessionState, PathInfo oldPath, PathInfo newPath) + { + SessionState = sessionState; + OldPath = oldPath; + NewPath = newPath; + } + + /// + /// Gets the path we changed location from. + /// + public PathInfo OldPath { get; internal set; } + + /// + /// Gets the path we changed location to. + /// + public PathInfo NewPath { get; internal set; } + + /// + /// Gets the session state instance for the current runspace. + /// + public SessionState SessionState { get; internal set; } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 index 95680dbe068..5ea2f278bc7 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 @@ -144,4 +144,40 @@ Describe "Set-Location" -Tags "CI" { { Set-Location - } | Should -Throw -ErrorId 'System.InvalidOperationException,Microsoft.PowerShell.Commands.SetLocationCommand' } } + + Context 'Test the LocationChangedAction event handler' { + + AfterEach { + $ExecutionContext.InvokeCommand.LocationChangedAction = $null + } + + It 'The LocationChangedAction should fire when changing location' { + $initialPath = $pwd + $oldPath = $null + $newPath = $null + $eventSessionState = $null + $eventRunspace = $null + $ExecutionContext.InvokeCommand.LocationChangedAction = { + (Get-Variable eventRunspace).Value = $this + (Get-Variable eventSessionState).Value = $_.SessionState + (Get-Variable oldPath).Value = $_.oldPath + (Get-Variable newPath).Value = $_.newPath + } + Set-Location .. + $newPath.Path | Should -Be $pwd.Path + $oldPath.Path | Should -Be $initialPath.Path + $eventSessionState | Should -Be $ExecutionContext.SessionState + $eventRunspace | Should -Be ([runspace]::DefaultRunspace) + } + + It 'Errors in the LocationChangedAction should be catchable but not fail the cd' { + $location = $PWD + Set-Location .. + $ExecutionContext.InvokeCommand.LocationChangedAction = { throw "Boom" } + # Verify that the exception occurred + { Set-Location $location } | Should Throw "Boom" + # But the location should still have changed + $PWD.Path | Should -Be $location.Path + } + } }