MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.Collections.ObjectModel;6using System.IO;7using System.Management.Automation.Language;8using System.Management.Automation.Runspaces;9using System.Management.Automation.Security;10using System.Text;11 12using Microsoft.PowerShell.Commands;13 14namespace System.Management.Automation15{16 /// <summary>17 /// Provides information for scripts that are directly executable by PowerShell18 /// but are not built into the runspace configuration.19 /// </summary>20 public class ExternalScriptInfo : CommandInfo, IScriptCommandInfo21 {22 #region ctor23 24 /// <summary>25 /// Creates an instance of the ExternalScriptInfo class with the specified name, and path.26 /// </summary>27 /// <param name="name">28 /// The name of the script.29 /// </param>30 /// <param name="path">31 /// The path to the script32 /// </param>33 /// <param name="context">34 /// The context of the currently running engine.35 /// </param>36 /// <exception cref="ArgumentNullException">37 /// If <paramref name="context"/> is null.38 /// </exception>39 /// <exception cref="ArgumentException">40 /// If <paramref name="path"/> is null or empty.41 /// </exception>42 internal ExternalScriptInfo(string name, string path, ExecutionContext context)43 : base(name, CommandTypes.ExternalScript, context)44 {45 if (string.IsNullOrEmpty(path))46 {47 throw PSTraceSource.NewArgumentException(nameof(path));48 }49 50 Diagnostics.Assert(IO.Path.IsPathRooted(path), "Caller makes sure that 'path' is already resolved.");51 52 // Path might contain short-name syntax such as 'DOCUME~1'. Use Path.GetFullPath to expand the short name53 _path = IO.Path.GetFullPath(path);54 CommonInitialization();55 }56 57 /// <summary>58 /// Creates an instance of ExternalScriptInfo that has no ExecutionContext.59 /// This is used exclusively to pass it to the AuthorizationManager that just uses the path parameter.60 /// </summary>61 /// <param name="name">62 /// The name of the script.63 /// </param>64 /// <param name="path">65 /// The path to the script66 /// </param>67 /// <exception cref="ArgumentException">68 /// If <paramref name="path"/> is null or empty.69 /// </exception>70 internal ExternalScriptInfo(string name, string path) : base(name, CommandTypes.ExternalScript)71 {72 if (string.IsNullOrEmpty(path))73 {74 throw PSTraceSource.NewArgumentException(nameof(path));75 }76 77 Diagnostics.Assert(IO.Path.IsPathRooted(path), "Caller makes sure that 'path' is already resolved.");78 79 // Path might contain short-name syntax such as 'DOCUME~1'. Use Path.GetFullPath to expand the short name80 _path = IO.Path.GetFullPath(path);81 CommonInitialization();82 }83 84 /// <summary>85 /// This is a copy constructor, used primarily for get-command.86 /// </summary>87 internal ExternalScriptInfo(ExternalScriptInfo other)88 : base(other)89 {90 _path = other._path;91 CommonInitialization();92 }93 94 /// <summary>95 /// Common initialization for all constructors.96 /// </summary>97 private void CommonInitialization()98 {99 // Assume external scripts are untrusted by default (for Get-Command, etc)100 // until we've actually parsed their script block.101 if (SystemPolicy.GetSystemLockdownPolicy() != SystemEnforcementMode.None)102 {103 // Get the lock down policy with no handle. This only impacts command discovery,104 // as the real language mode assignment will be done when we read the script105 // contents.106 switch (SystemPolicy.GetLockdownPolicy(_path, null))107 {108 case SystemEnforcementMode.None:109 DefiningLanguageMode = PSLanguageMode.FullLanguage;110 break;111 112 case SystemEnforcementMode.Audit:113 // For policy audit mode, language mode is set to CL but audit messages are emitted to log114 // instead of applying restrictions.115 DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage;116 break;117 118 case SystemEnforcementMode.Enforce:119 DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage;120 break;121 }122 }123 }124 125 /// <summary>126 /// Create a copy of commandInfo for GetCommandCommand so that we can generate parameter127 /// sets based on an argument list (so we can get the dynamic parameters.)128 /// </summary>129 internal override CommandInfo CreateGetCommandCopy(object[] argumentList)130 {131 ExternalScriptInfo copy = new ExternalScriptInfo(this) { IsGetCommandCopy = true, Arguments = argumentList };132 return copy;133 }134 135 #endregion ctor136 137 internal override HelpCategory HelpCategory138 {139 get { return HelpCategory.ExternalScript; }140 }141 142 /// <summary>143 /// Gets the path to the script file.144 /// </summary>145 public string Path146 {147 get { return _path; }148 }149 150 private readonly string _path = string.Empty;151 152 /// <summary>153 /// Gets the path to the script file.154 /// </summary>155 public override string Definition156 {157 get { return Path; }158 }159 160 /// <summary>161 /// Gets the source of this command.162 /// </summary>163 public override string Source164 {165 get { return this.Definition; }166 }167 168 /// <summary>169 /// Returns the syntax of a command.170 /// </summary>171 internal override string Syntax172 {173 get174 {175 StringBuilder synopsis = new StringBuilder();176 177 foreach (CommandParameterSetInfo parameterSet in ParameterSets)178 {179 synopsis.AppendLine(180 string.Format(181 Globalization.CultureInfo.CurrentCulture,182 "{0} {1}",183 Name,184 parameterSet));185 }186 187 return synopsis.ToString();188 }189 }190 191 /// <summary>192 /// Determine the visibility for this script...193 /// </summary>194 public override SessionStateEntryVisibility Visibility195 {196 get197 {198 if (Context == null)199 {200 return SessionStateEntryVisibility.Public;201 }202 203 return Context.EngineSessionState.CheckScriptVisibility(_path);204 }205 206 set207 {208 throw PSTraceSource.NewNotImplementedException();209 }210 }211 212 /// <summary>213 /// The script block that represents the external script.214 /// </summary>215 public ScriptBlock ScriptBlock216 {217 get218 {219 if (_scriptBlock == null)220 {221 // Skip ShouldRun check for .psd1 files.222 // Use ValidateScriptInfo() for explicitly validating the checkpolicy for psd1 file.223 //224 if (!_path.EndsWith(".psd1", StringComparison.OrdinalIgnoreCase))225 {226 ValidateScriptInfo(null);227 }228 229 // parse the script into an expression tree...230 ScriptBlock newScriptBlock = ParseScriptContents(new Parser(), _path, ScriptContents, DefiningLanguageMode);231 this.ScriptBlock = newScriptBlock;232 }233 234 return _scriptBlock;235 }236 237 private set238 {239 _scriptBlock = value;240 if (value != null)241 {242 _scriptBlock.LanguageMode = this.DefiningLanguageMode;243 }244 }245 }246 247 private ScriptBlock _scriptBlock;248 private ScriptBlockAst _scriptBlockAst;249 250 private static ScriptBlock ParseScriptContents(Parser parser, string fileName, string fileContents, PSLanguageMode? definingLanguageMode)251 {252 // If we are in ConstrainedLanguage mode but the defining language mode is FullLanguage, then we need253 // to parse the script contents in FullLanguage mode context. Otherwise we will get bogus parsing errors254 // such as "Configuration keyword not allowed".255 if (definingLanguageMode.HasValue && (definingLanguageMode == PSLanguageMode.FullLanguage))256 {257 var context = LocalPipeline.GetExecutionContextFromTLS();258 if ((context != null) && (context.LanguageMode == PSLanguageMode.ConstrainedLanguage))259 {260 context.LanguageMode = PSLanguageMode.FullLanguage;261 try262 {263 return ScriptBlock.Create(parser, fileName, fileContents);264 }265 finally266 {267 context.LanguageMode = PSLanguageMode.ConstrainedLanguage;268 }269 }270 }271 272 return ScriptBlock.Create(parser, fileName, fileContents);273 }274 275 internal ScriptBlockAst GetScriptBlockAst()276 {277 var scriptContents = ScriptContents;278 if (_scriptBlock == null)279 {280 this.ScriptBlock = ScriptBlock.TryGetCachedScriptBlock(_path, scriptContents);281 }282 283 if (_scriptBlock != null)284 {285 return (ScriptBlockAst)_scriptBlock.Ast;286 }287 288 if (_scriptBlockAst == null)289 {290 ParseError[] errors;291 Parser parser = new Parser();292 293 // If we are in ConstrainedLanguage mode but the defining language mode is FullLanguage, then we need294 // to parse the script contents in FullLanguage mode context. Otherwise we will get bogus parsing errors295 // such as "Configuration or Class keyword not allowed".296 var context = LocalPipeline.GetExecutionContextFromTLS();297 if (context != null && context.LanguageMode == PSLanguageMode.ConstrainedLanguage &&298 DefiningLanguageMode == PSLanguageMode.FullLanguage)299 {300 context.LanguageMode = PSLanguageMode.FullLanguage;301 try302 {303 _scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default);304 }305 finally306 {307 context.LanguageMode = PSLanguageMode.ConstrainedLanguage;308 }309 }310 else311 {312 _scriptBlockAst = parser.Parse(_path, ScriptContents, null, out errors, ParseMode.Default);313 }314 315 if (errors.Length == 0)316 {317 this.ScriptBlock = new ScriptBlock(_scriptBlockAst, isFilter: false);318 ScriptBlock.CacheScriptBlock(_scriptBlock.Clone(), _path, scriptContents);319 }320 }321 322 return _scriptBlockAst;323 }324 325 /// <summary>326 /// Validates the external script info.327 /// </summary>328 /// <param name="host"></param>329 public void ValidateScriptInfo(Host.PSHost host)330 {331 if (!_signatureChecked)332 {333 ExecutionContext context = Context ?? LocalPipeline.GetExecutionContextFromTLS();334 335 ReadScriptContents();336 337 // We have no way to check the signature w/o context because we don't have338 // an AuthorizationManager. This can happen during initialization when trying339 // to get the CommandMetadata for a script (either to prepopulate the metadata340 // or creating a proxy). If context can be null under any other circumstances,341 // we need to make sure it's acceptable if the parser is invoked on unsigned scripts.342 if (context != null)343 {344 CommandDiscovery.ShouldRun(context, host, this, CommandOrigin.Internal);345 _signatureChecked = true;346 }347 }348 }349 350 /// <summary>351 /// The output type(s) is specified in the script block.352 /// </summary>353 public override ReadOnlyCollection<PSTypeName> OutputType354 {355 get { return ScriptBlock.OutputType; }356 }357 358 internal bool SignatureChecked359 {360 set { _signatureChecked = value; }361 }362 363 private bool _signatureChecked;364 365 #region Internal366 367 /// <summary>368 /// The command metadata for the script.369 /// </summary>370 internal override CommandMetadata CommandMetadata371 {372 get373 {374 return _commandMetadata ??=375 new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS());376 }377 }378 379 private CommandMetadata _commandMetadata;380 381 /// <summary>382 /// True if the command has dynamic parameters, false otherwise.383 /// </summary>384 internal override bool ImplementsDynamicParameters385 {386 get387 {388 try389 {390 return ScriptBlock.HasDynamicParameters;391 }392 catch (ParseException) { }393 catch (ScriptRequiresException) { }394 395 // If we got here, there was some sort of parsing exception. We'll just396 // ignore it and assume the script does not implement dynamic parameters.397 // Furthermore, we'll clear out the fields so that the next attempt to398 // access ScriptBlock will result in an exception that doesn't get ignored.399 _scriptBlock = null;400 _scriptContents = null;401 402 return false;403 }404 }405 406 #endregion Internal407 408 private ScriptRequirements GetRequiresData()409 {410 return GetScriptBlockAst().ScriptRequirements;411 }412 413 internal string RequiresApplicationID414 {415 get416 {417 var data = GetRequiresData();418 return data?.RequiredApplicationId;419 }420 }421 422 internal uint ApplicationIDLineNumber423 {424 get { return 0; }425 }426 427 internal Version RequiresPSVersion428 {429 get430 {431 var data = GetRequiresData();432 return data?.RequiredPSVersion;433 }434 }435 436 internal IEnumerable<string> RequiresPSEditions437 {438 get439 {440 var data = GetRequiresData();441 return data?.RequiredPSEditions;442 }443 }444 445 internal IEnumerable<ModuleSpecification> RequiresModules446 {447 get448 {449 var data = GetRequiresData();450 return data?.RequiredModules;451 }452 }453 454 internal bool RequiresElevation455 {456 get457 {458 var data = GetRequiresData();459 return data != null && data.IsElevationRequired;460 }461 }462 463 internal uint PSVersionLineNumber464 {465 get { return 0; }466 }467 468 /// <summary>469 /// Gets the original contents of the script.470 /// </summary>471 public string ScriptContents472 {473 get474 {475 if (_scriptContents == null)476 {477 ReadScriptContents();478 }479 480 return _scriptContents;481 }482 }483 484 private string _scriptContents;485 486 /// <summary>487 /// Gets the original encoding of the script.488 /// </summary>489 public Encoding OriginalEncoding490 {491 get492 {493 if (_scriptContents == null)494 {495 ReadScriptContents();496 }497 498 return _originalEncoding;499 }500 }501 502 private Encoding _originalEncoding;503 504 private void ReadScriptContents()505 {506 if (_scriptContents == null)507 {508 // make sure we can actually load the script and that it's non-empty509 // before we call it.510 511 // Note, although we are passing ASCII as the encoding, the StreamReader512 // class still obeys the byte order marks at the beginning of the file513 // if present. If not present, then ASCII is used as the default encoding.514 515 try516 {517 using (FileStream readerStream = new FileStream(_path, FileMode.Open, FileAccess.Read))518 {519 using (StreamReader scriptReader = new StreamReader(readerStream, Encoding.Default))520 {521 _scriptContents = scriptReader.ReadToEnd();522 _originalEncoding = scriptReader.CurrentEncoding;523 524 // Check this file against any system wide enforcement policies.525 SystemScriptFileEnforcement filePolicyEnforcement = SystemPolicy.GetFilePolicyEnforcement(_path, readerStream);526 switch (filePolicyEnforcement)527 {528 case SystemScriptFileEnforcement.None:529 if (Context != null)530 {531 DefiningLanguageMode = Context.LanguageMode;532 }533 break;534 535 case SystemScriptFileEnforcement.Allow:536 DefiningLanguageMode = PSLanguageMode.FullLanguage;537 break;538 539 case SystemScriptFileEnforcement.AllowConstrained:540 DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage;541 break;542 543 case SystemScriptFileEnforcement.AllowConstrainedAudit:544 SystemPolicy.LogWDACAuditMessage(545 context: Context,546 title: SecuritySupportStrings.ExternalScriptWDACLogTitle,547 message: string.Format(Globalization.CultureInfo.CurrentUICulture, SecuritySupportStrings.ExternalScriptWDACLogMessage, _path),548 fqid: "ScriptFileNotTrustedByPolicy");549 // We set the language mode to Constrained Language, even though in policy audit mode no restrictions are applied550 // and instead an audit log message is generated wherever a restriction would be applied.551 DefiningLanguageMode = PSLanguageMode.ConstrainedLanguage;552 break;553 554 case SystemScriptFileEnforcement.Block:555 throw new PSSecurityException(556 string.Format(557 Globalization.CultureInfo.CurrentUICulture,558 SecuritySupportStrings.ScriptFileBlockedBySystemPolicy,559 _path));560 561 default:562 throw new PSSecurityException(563 string.Format(564 Globalization.CultureInfo.CurrentUICulture,565 SecuritySupportStrings.UnknownSystemScriptFileEnforcement,566 filePolicyEnforcement));567 }568 }569 }570 }571 catch (ArgumentException e)572 {573 // This catches PSArgumentException as well.574 ThrowCommandNotFoundException(e);575 }576 catch (IOException e)577 {578 ThrowCommandNotFoundException(e);579 }580 catch (NotSupportedException e)581 {582 ThrowCommandNotFoundException(e);583 }584 catch (UnauthorizedAccessException e)585 {586 // this is unadvertised exception thrown by the StreamReader ctor when587 // no permission to read the script file588 ThrowCommandNotFoundException(e);589 }590 }591 }592 593 private static void ThrowCommandNotFoundException(Exception innerException)594 {595 CommandNotFoundException cmdE = new CommandNotFoundException(innerException.Message, innerException);596 throw cmdE;597 }598 }599 600 /// <summary>601 /// Thrown when fail to parse #requires statements. Caught by CommandDiscovery.602 /// </summary>603 internal class ScriptRequiresSyntaxException : ScriptRequiresException604 {605 internal ScriptRequiresSyntaxException(string message)606 : base(message)607 {608 }609 }610 611 /// <summary>612 /// Defines the name and version tuple of a PSSnapin.613 /// </summary>614 public class PSSnapInSpecification615 {616 internal PSSnapInSpecification(string psSnapinName)617 {618 PSSnapInInfo.VerifyPSSnapInFormatThrowIfError(psSnapinName);619 Name = psSnapinName;620 Version = null;621 }622 623 /// <summary>624 /// The name of the snapin.625 /// </summary>626 public string Name { get; internal set; }627 628 /// <summary>629 /// The version of the snapin.630 /// </summary>631 public Version Version { get; internal set; }632 }633}634 