mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
603 lines
20 KiB
C#
603 lines
20 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Collections.ObjectModel;
|
|
using System.Management.Automation;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Management.Automation.Internal;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
#pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings
|
|
|
|
namespace Microsoft.PowerShell.Commands
|
|
{
|
|
#region ConsoleCmdletsBase
|
|
/// <summary>
|
|
/// Base class for all the Console related cmdlets.
|
|
/// </summary>
|
|
public abstract class ConsoleCmdletsBase : PSCmdlet
|
|
{
|
|
/// <summary>
|
|
/// Runspace configuration for the current engine
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Console cmdlets need <see cref="RunspaceConfigForSingleShell"/> object to work with.
|
|
/// </remarks>
|
|
internal RunspaceConfigForSingleShell Runspace
|
|
{
|
|
get
|
|
{
|
|
RunspaceConfigForSingleShell runSpace = Context.RunspaceConfiguration as RunspaceConfigForSingleShell;
|
|
return runSpace;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// InitialSessionState for the current engine
|
|
/// </summary>
|
|
internal InitialSessionState InitialSessionState
|
|
{
|
|
get
|
|
{
|
|
return Context.InitialSessionState;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Throws a terminating error.
|
|
/// </summary>
|
|
/// <param name="targetObject">Object which caused this exception.</param>
|
|
/// <param name="errorId">ErrorId for this error.</param>
|
|
/// <param name="innerException">Complete exception object.</param>
|
|
/// <param name="category">ErrorCategory for this exception.</param>
|
|
internal void ThrowError(
|
|
Object targetObject,
|
|
string errorId,
|
|
Exception innerException,
|
|
ErrorCategory category)
|
|
{
|
|
ThrowTerminatingError(new ErrorRecord(innerException, errorId, category, targetObject));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region export-console
|
|
|
|
/// <summary>
|
|
/// Class that implements export-console cmdlet.
|
|
/// </summary>
|
|
[Cmdlet(VerbsData.Export, "Console", SupportsShouldProcess = true, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113298")]
|
|
public sealed class ExportConsoleCommand : ConsoleCmdletsBase
|
|
{
|
|
#region Parameters
|
|
|
|
/// <summary>
|
|
/// Property that gets/sets console file name.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If a parameter is not supplied then the file represented by $console
|
|
/// will be used for saving.
|
|
/// </remarks>
|
|
[Parameter(Position = 0, Mandatory = false, ValueFromPipeline = true,
|
|
ValueFromPipelineByPropertyName = true)]
|
|
[Alias("PSPath")]
|
|
public string Path
|
|
{
|
|
get
|
|
{
|
|
return _fileName;
|
|
}
|
|
|
|
set
|
|
{
|
|
_fileName = value;
|
|
}
|
|
}
|
|
private string _fileName;
|
|
|
|
/// <summary>
|
|
/// Property that sets force parameter. This will reset the read-only
|
|
/// attribute on an existing file before deleting it.
|
|
/// </summary>
|
|
[Parameter()]
|
|
public SwitchParameter Force
|
|
{
|
|
get
|
|
{
|
|
return _force;
|
|
}
|
|
set
|
|
{
|
|
_force = value;
|
|
}
|
|
}
|
|
private bool _force;
|
|
|
|
/// <summary>
|
|
/// Property that prevents file overwrite.
|
|
/// </summary>
|
|
[Parameter()]
|
|
[Alias("NoOverwrite")]
|
|
public SwitchParameter NoClobber
|
|
{
|
|
get
|
|
{
|
|
return _noclobber;
|
|
}
|
|
set
|
|
{
|
|
_noclobber = value;
|
|
}
|
|
}
|
|
private bool _noclobber;
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
/// <summary>
|
|
/// Saves the current console info into a file.
|
|
/// </summary>
|
|
protected override void ProcessRecord()
|
|
{
|
|
// Get filename..
|
|
string file = GetFileName();
|
|
|
|
// if file is null or empty..prompt the user for filename
|
|
if (string.IsNullOrEmpty(file))
|
|
{
|
|
file = PromptUserForFile();
|
|
}
|
|
|
|
// if file is still empty..write error and back out..
|
|
if (string.IsNullOrEmpty(file))
|
|
{
|
|
PSArgumentException ae = PSTraceSource.NewArgumentException("file", ConsoleInfoErrorStrings.FileNameNotResolved);
|
|
ThrowError(file, "FileNameNotResolved", ae, ErrorCategory.InvalidArgument);
|
|
}
|
|
|
|
if (WildcardPattern.ContainsWildcardCharacters(file))
|
|
{
|
|
ThrowError(file, "WildCardNotSupported",
|
|
PSTraceSource.NewInvalidOperationException(ConsoleInfoErrorStrings.ConsoleFileWildCardsNotSupported,
|
|
file), ErrorCategory.InvalidOperation);
|
|
}
|
|
|
|
// Ofcourse, you cant write to a file from HKLM: etc..
|
|
string resolvedPath = ResolveProviderAndPath(file);
|
|
|
|
// If resolvedPath is empty just return..
|
|
if (string.IsNullOrEmpty(resolvedPath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check whether the file ends with valid extension
|
|
if (!resolvedPath.EndsWith(StringLiterals.PowerShellConsoleFileExtension,
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// file does not end with proper extension..create one..
|
|
resolvedPath = resolvedPath + StringLiterals.PowerShellConsoleFileExtension;
|
|
}
|
|
|
|
if (!ShouldProcess(this.Path)) // should this be resolvedPath?
|
|
return;
|
|
|
|
//check if destination file exists.
|
|
if (File.Exists(resolvedPath))
|
|
{
|
|
if (NoClobber)
|
|
{
|
|
string message = StringUtil.Format(
|
|
ConsoleInfoErrorStrings.FileExistsNoClobber,
|
|
resolvedPath,
|
|
"NoClobber"); // prevents localization
|
|
Exception uae = new UnauthorizedAccessException(message);
|
|
ErrorRecord errorRecord = new ErrorRecord(
|
|
uae, "NoClobber", ErrorCategory.ResourceExists, resolvedPath);
|
|
// NOTE: this call will throw
|
|
ThrowTerminatingError(errorRecord);
|
|
}
|
|
// Check if the file is read-only
|
|
System.IO.FileAttributes attrib = System.IO.File.GetAttributes(resolvedPath);
|
|
if ((attrib & System.IO.FileAttributes.ReadOnly) == System.IO.FileAttributes.ReadOnly)
|
|
{
|
|
if (Force)
|
|
{
|
|
RemoveFileThrowIfError(resolvedPath);
|
|
// Note, we do not attempt to set read-only on the new file
|
|
}
|
|
else
|
|
{
|
|
ThrowError(file, "ConsoleFileReadOnly",
|
|
PSTraceSource.NewArgumentException(file, ConsoleInfoErrorStrings.ConsoleFileReadOnly, resolvedPath),
|
|
ErrorCategory.InvalidArgument);
|
|
}
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
if (this.Runspace != null)
|
|
{
|
|
this.Runspace.SaveAsConsoleFile(resolvedPath);
|
|
}
|
|
else if (InitialSessionState != null)
|
|
{
|
|
this.InitialSessionState.SaveAsConsoleFile(resolvedPath);
|
|
}
|
|
else
|
|
{
|
|
Dbg.Assert(false, "Both RunspaceConfiguration and InitialSessionState should not be null");
|
|
throw PSTraceSource.NewInvalidOperationException(ConsoleInfoErrorStrings.CmdletNotAvailable);
|
|
}
|
|
}
|
|
catch (PSArgumentException mae)
|
|
{
|
|
ThrowError(resolvedPath,
|
|
"PathNotAbsolute", mae, ErrorCategory.InvalidArgument);
|
|
}
|
|
catch (PSArgumentNullException mane)
|
|
{
|
|
ThrowError(resolvedPath,
|
|
"PathNull", mane, ErrorCategory.InvalidArgument);
|
|
}
|
|
catch (ArgumentException ae)
|
|
{
|
|
ThrowError(resolvedPath,
|
|
"InvalidCharactersInPath", ae, ErrorCategory.InvalidArgument);
|
|
}
|
|
|
|
// looks like saving succeeded.
|
|
// Now try changing $console
|
|
Exception e = null;
|
|
try
|
|
{
|
|
//Update $Console variable
|
|
Context.EngineSessionState.SetConsoleVariable();
|
|
}
|
|
catch (ArgumentNullException ane)
|
|
{
|
|
e = ane;
|
|
}
|
|
catch (ArgumentOutOfRangeException aor)
|
|
{
|
|
e = aor;
|
|
}
|
|
catch (ArgumentException ae)
|
|
{
|
|
e = ae;
|
|
}
|
|
catch (SessionStateUnauthorizedAccessException sue)
|
|
{
|
|
e = sue;
|
|
}
|
|
catch (ProviderNotFoundException pnf)
|
|
{
|
|
e = pnf;
|
|
}
|
|
catch (System.Management.Automation.DriveNotFoundException dnfe)
|
|
{
|
|
e = dnfe;
|
|
}
|
|
catch (NotSupportedException ne)
|
|
{
|
|
e = ne;
|
|
}
|
|
catch (ProviderInvocationException pin)
|
|
{
|
|
e = pin;
|
|
}
|
|
|
|
if (e != null)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(e,
|
|
ConsoleInfoErrorStrings.ConsoleVariableCannotBeSet, resolvedPath);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Removes file specified by destination
|
|
/// </summary>
|
|
/// <param name="destination">Absolute path of the file to be removed.</param>
|
|
private void RemoveFileThrowIfError(string destination)
|
|
{
|
|
Diagnostics.Assert(System.IO.Path.IsPathRooted(destination),
|
|
"RemoveFile expects an absolute path");
|
|
|
|
System.IO.FileInfo destfile = new System.IO.FileInfo(destination);
|
|
if (destfile != null)
|
|
{
|
|
Exception e = null;
|
|
try
|
|
{
|
|
//Make sure the file is not read only
|
|
destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden);
|
|
destfile.Delete();
|
|
}
|
|
catch (FileNotFoundException fnf)
|
|
{
|
|
e = fnf;
|
|
}
|
|
catch (DirectoryNotFoundException dnf)
|
|
{
|
|
e = dnf;
|
|
}
|
|
catch (UnauthorizedAccessException uac)
|
|
{
|
|
e = uac;
|
|
}
|
|
catch (System.Security.SecurityException se)
|
|
{
|
|
e = se;
|
|
}
|
|
catch (ArgumentNullException ane)
|
|
{
|
|
e = ane;
|
|
}
|
|
catch (ArgumentException ae)
|
|
{
|
|
e = ae;
|
|
}
|
|
catch (PathTooLongException pe)
|
|
{
|
|
e = pe;
|
|
}
|
|
catch (NotSupportedException ns)
|
|
{
|
|
e = ns;
|
|
}
|
|
catch (IOException ioe)
|
|
{
|
|
e = ioe;
|
|
}
|
|
|
|
if (e != null)
|
|
{
|
|
throw PSTraceSource.NewInvalidOperationException(e,
|
|
ConsoleInfoErrorStrings.ExportConsoleCannotDeleteFile,
|
|
destfile);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the specified path and verifies the path belongs to
|
|
/// FileSystemProvider.
|
|
/// </summary>
|
|
/// <param name="path">Path to resolve</param>
|
|
/// <returns>A fully qualified string representing filename.</returns>
|
|
private string ResolveProviderAndPath(string path)
|
|
{
|
|
// Construct cmdletprovidercontext
|
|
CmdletProviderContext cmdContext = new CmdletProviderContext(this);
|
|
// First resolve path
|
|
PathInfo resolvedPath = ResolvePath(path, true, cmdContext);
|
|
|
|
// Check whether this is FileSystemProvider..
|
|
if (resolvedPath != null)
|
|
{
|
|
if (resolvedPath.Provider.ImplementingType == typeof(FileSystemProvider))
|
|
{
|
|
return resolvedPath.Path;
|
|
}
|
|
|
|
throw PSTraceSource.NewInvalidOperationException(ConsoleInfoErrorStrings.ProviderNotSupported, resolvedPath.Provider.Name);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the specified path to PathInfo objects
|
|
/// </summary>
|
|
///
|
|
/// <param name="pathToResolve">
|
|
/// The path to be resolved. Each path may contain glob characters.
|
|
/// </param>
|
|
///
|
|
/// <param name="allowNonexistingPaths">
|
|
/// If true, resolves the path even if it doesn't exist.
|
|
/// </param>
|
|
///
|
|
/// <param name="currentCommandContext">
|
|
/// The context under which the command is running.
|
|
/// </param>
|
|
///
|
|
/// <returns>
|
|
/// A string representing the resolved path.
|
|
/// </returns>
|
|
///
|
|
private PathInfo ResolvePath(
|
|
string pathToResolve,
|
|
bool allowNonexistingPaths,
|
|
CmdletProviderContext currentCommandContext)
|
|
{
|
|
Collection<PathInfo> results = new Collection<PathInfo>();
|
|
|
|
try
|
|
{
|
|
// First resolve path
|
|
Collection<PathInfo> pathInfos =
|
|
SessionState.Path.GetResolvedPSPathFromPSPath(
|
|
pathToResolve,
|
|
currentCommandContext);
|
|
|
|
foreach (PathInfo pathInfo in pathInfos)
|
|
{
|
|
results.Add(pathInfo);
|
|
}
|
|
}
|
|
catch (PSNotSupportedException notSupported)
|
|
{
|
|
WriteError(
|
|
new ErrorRecord(
|
|
notSupported.ErrorRecord,
|
|
notSupported));
|
|
}
|
|
catch (System.Management.Automation.DriveNotFoundException driveNotFound)
|
|
{
|
|
WriteError(
|
|
new ErrorRecord(
|
|
driveNotFound.ErrorRecord,
|
|
driveNotFound));
|
|
}
|
|
catch (ProviderNotFoundException providerNotFound)
|
|
{
|
|
WriteError(
|
|
new ErrorRecord(
|
|
providerNotFound.ErrorRecord,
|
|
providerNotFound));
|
|
}
|
|
catch (ItemNotFoundException pathNotFound)
|
|
{
|
|
if (allowNonexistingPaths)
|
|
{
|
|
ProviderInfo provider = null;
|
|
System.Management.Automation.PSDriveInfo drive = null;
|
|
string unresolvedPath =
|
|
SessionState.Path.GetUnresolvedProviderPathFromPSPath(
|
|
pathToResolve,
|
|
currentCommandContext,
|
|
out provider,
|
|
out drive);
|
|
|
|
PathInfo pathInfo =
|
|
new PathInfo(
|
|
drive,
|
|
provider,
|
|
unresolvedPath,
|
|
SessionState);
|
|
results.Add(pathInfo);
|
|
}
|
|
else
|
|
{
|
|
WriteError(
|
|
new ErrorRecord(
|
|
pathNotFound.ErrorRecord,
|
|
pathNotFound));
|
|
}
|
|
}
|
|
|
|
if (results.Count == 1)
|
|
{
|
|
return results[0];
|
|
}
|
|
else if (results.Count > 1)
|
|
{
|
|
Exception e = PSTraceSource.NewNotSupportedException();
|
|
WriteError(
|
|
new ErrorRecord(e,
|
|
"NotSupported",
|
|
ErrorCategory.NotImplemented,
|
|
results));
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
} // ResolvePath
|
|
|
|
/// <summary>
|
|
/// Gets the filename for the current operation. If Name parameter is empty
|
|
/// checks $console. If $console is not present, prompts user?
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A string representing filename.
|
|
/// If filename cannot be deduced returns null.
|
|
/// </returns>
|
|
/// <exception cref="PSArgumentException">
|
|
/// 1. $console points to an PSObject that cannot be converted to string.
|
|
/// </exception>
|
|
private string GetFileName()
|
|
{
|
|
if (!string.IsNullOrEmpty(_fileName))
|
|
{
|
|
// if user specified input..just return
|
|
return _fileName;
|
|
}
|
|
// no input is specified..
|
|
// check whether $console is set
|
|
PSVariable consoleVariable = Context.SessionState.PSVariable.Get(SpecialVariables.ConsoleFileName);
|
|
if (consoleVariable == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
string consoleValue = consoleVariable.Value as string;
|
|
|
|
if (consoleValue == null)
|
|
{
|
|
// $console is not in string format
|
|
// Check whether it is in PSObject format
|
|
PSObject consolePSObject = consoleVariable.Value as PSObject;
|
|
|
|
if ((consolePSObject != null) && ((consolePSObject.BaseObject as string) != null))
|
|
{
|
|
consoleValue = consolePSObject.BaseObject as string;
|
|
}
|
|
}
|
|
|
|
if (consoleValue != null)
|
|
{
|
|
// ConsoleFileName variable is found..
|
|
return consoleValue;
|
|
}
|
|
|
|
throw PSTraceSource.NewArgumentException("fileName", ConsoleInfoErrorStrings.ConsoleCannotBeConvertedToString);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prompt user for filename.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// User input in string format.
|
|
/// If user chooses not to export, an empty string is returned.
|
|
/// </returns>
|
|
/// <remarks>No exception is thrown</remarks>
|
|
private string PromptUserForFile()
|
|
{
|
|
// ask user what to do..
|
|
if (ShouldContinue(
|
|
ConsoleInfoErrorStrings.PromptForExportConsole,
|
|
null))
|
|
{
|
|
string caption = StringUtil.Format(ConsoleInfoErrorStrings.FileNameCaptionForExportConsole, "export-console");
|
|
string message = ConsoleInfoErrorStrings.FileNamePromptMessage;
|
|
|
|
// Construct a field description object of required parameters
|
|
Collection<System.Management.Automation.Host.FieldDescription> desc = new Collection<System.Management.Automation.Host.FieldDescription>();
|
|
desc.Add(new System.Management.Automation.Host.FieldDescription("Name"));
|
|
|
|
// get user input from the host
|
|
System.Collections.Generic.Dictionary<string, PSObject> returnValue =
|
|
this.PSHostInternal.UI.Prompt(caption, message, desc);
|
|
|
|
if ((returnValue != null) && (returnValue["Name"] != null))
|
|
{
|
|
return (returnValue["Name"].BaseObject as string);
|
|
}
|
|
|
|
// We dont have any input..
|
|
return string.Empty;
|
|
}
|
|
|
|
// If user chooses not to export, return empty string.
|
|
return string.Empty;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|