MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Globalization;8using System.Management.Automation;9using System.Security.Cryptography.Pkcs;10using System.Security.Cryptography.X509Certificates;11using System.Text;12 13namespace Microsoft.PowerShell.Commands14{15 /// <summary>16 /// Defines the implementation of the 'Protect-CmsMessage' cmdlet.17 ///18 /// This cmdlet generates a new encrypted CMS message given the19 /// recipient and content supplied.20 /// </summary>21 [Cmdlet(VerbsSecurity.Protect, "CmsMessage", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096826", DefaultParameterSetName = "ByContent")]22 [OutputType(typeof(string))]23 public sealed class ProtectCmsMessageCommand : PSCmdlet24 {25 /// <summary>26 /// Gets or sets the recipient of the CMS Message.27 /// </summary>28 [Parameter(Position = 0, Mandatory = true)]29 public CmsMessageRecipient[] To30 {31 get; set;32 }33 34 /// <summary>35 /// Gets or sets the content of the CMS Message.36 /// </summary>37 [Parameter(Position = 1, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "ByContent")]38 [AllowNull]39 [AllowEmptyString]40 public PSObject Content41 {42 get;43 set;44 }45 46 private readonly PSDataCollection<PSObject> _inputObjects = new();47 48 /// <summary>49 /// Gets or sets the content of the CMS Message by path.50 /// </summary>51 [Parameter(Position = 1, Mandatory = true, ParameterSetName = "ByPath")]52 public string Path53 {54 get;55 set;56 }57 58 /// <summary>59 /// Gets or sets the content of the CMS Message by literal path.60 /// </summary>61 [Parameter(Position = 1, Mandatory = true, ParameterSetName = "ByLiteralPath")]62 public string LiteralPath63 {64 get;65 set;66 }67 68 private string _resolvedPath = null;69 70 /// <summary>71 /// Emits the protected message to a file path.72 /// </summary>73 [Parameter(Position = 2)]74 public string OutFile75 {76 get;77 set;78 }79 80 private string _resolvedOutFile = null;81 82 /// <summary>83 /// Validate / convert arguments.84 /// </summary>85 protected override void BeginProcessing()86 {87 // Validate Path88 if (!string.IsNullOrEmpty(Path))89 {90 ProviderInfo provider = null;91 Collection<string> resolvedPaths = GetResolvedProviderPathFromPSPath(Path, out provider);92 93 // Ensure the path is a single path from the file system provider94 if ((resolvedPaths.Count > 1) ||95 (!string.Equals(provider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase)))96 {97 ErrorRecord error = new(98 new ArgumentException(99 string.Format(100 CultureInfo.InvariantCulture,101 CmsCommands.FilePathMustBeFileSystemPath,102 Path)),103 "FilePathMustBeFileSystemPath",104 ErrorCategory.ObjectNotFound,105 provider);106 ThrowTerminatingError(error);107 }108 109 _resolvedPath = resolvedPaths[0];110 }111 112 if (!string.IsNullOrEmpty(LiteralPath))113 {114 // Validate that the path exists115 SessionState.InvokeProvider.Item.Get(new string[] { LiteralPath }, false, true);116 _resolvedPath = LiteralPath;117 }118 119 // Validate OutFile120 if (!string.IsNullOrEmpty(OutFile))121 {122 _resolvedOutFile = GetUnresolvedProviderPathFromPSPath(OutFile);123 }124 }125 126 /// <summary>127 /// Processes records from the input pipeline.128 /// For each input object, the command encrypts129 /// and exports the object.130 /// </summary>131 protected override void ProcessRecord()132 {133 if (string.Equals("ByContent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))134 {135 _inputObjects.Add(Content);136 }137 }138 139 /// <summary>140 /// Encrypts and outputs the message.141 /// </summary>142 protected override void EndProcessing()143 {144 byte[] contentBytes = null;145 146 if (_inputObjects.Count > 0)147 {148 StringBuilder outputString = new();149 150 Collection<PSObject> output = System.Management.Automation.PowerShell.Create()151 .AddCommand("Microsoft.PowerShell.Utility\\Out-String")152 .AddParameter("Stream")153 .Invoke(_inputObjects);154 155 foreach (PSObject outputObject in output)156 {157 if (outputString.Length > 0)158 {159 outputString.AppendLine();160 }161 162 outputString.Append(outputObject);163 }164 165 contentBytes = System.Text.Encoding.UTF8.GetBytes(outputString.ToString());166 }167 else168 {169 contentBytes = System.IO.File.ReadAllBytes(_resolvedPath);170 }171 172 ErrorRecord terminatingError = null;173 string encodedContent = CmsUtils.Encrypt(contentBytes, To, this.SessionState, out terminatingError);174 175 if (terminatingError != null)176 {177 ThrowTerminatingError(terminatingError);178 }179 180 if (string.IsNullOrEmpty(_resolvedOutFile))181 {182 WriteObject(encodedContent);183 }184 else185 {186 System.IO.File.WriteAllText(_resolvedOutFile, encodedContent);187 }188 }189 }190 191 /// <summary>192 /// Defines the implementation of the 'Get-CmsMessage' cmdlet.193 ///194 /// This cmdlet retrieves information about an encrypted CMS195 /// message.196 /// </summary>197 [Cmdlet(VerbsCommon.Get, "CmsMessage", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096598")]198 [OutputType(typeof(EnvelopedCms))]199 public sealed class GetCmsMessageCommand : PSCmdlet200 {201 /// <summary>202 /// Gets or sets the content of the CMS Message.203 /// </summary>204 [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "ByContent")]205 [AllowNull]206 [AllowEmptyString]207 public string Content208 {209 get;210 set;211 }212 213 private readonly StringBuilder _contentBuffer = new();214 215 /// <summary>216 /// Gets or sets the CMS Message by path.217 /// </summary>218 [Parameter(Position = 1, Mandatory = true, ParameterSetName = "ByPath")]219 public string Path220 {221 get;222 set;223 }224 225 /// <summary>226 /// Gets or sets the CMS Message by literal path.227 /// </summary>228 [Parameter(Position = 1, Mandatory = true, ParameterSetName = "ByLiteralPath")]229 public string LiteralPath230 {231 get;232 set;233 }234 235 private string _resolvedPath = null;236 237 /// <summary>238 /// Validate / convert arguments.239 /// </summary>240 protected override void BeginProcessing()241 {242 // Validate Path243 if (!string.IsNullOrEmpty(Path))244 {245 ProviderInfo provider = null;246 Collection<string> resolvedPaths = GetResolvedProviderPathFromPSPath(Path, out provider);247 248 // Ensure the path is a single path from the file system provider249 if ((resolvedPaths.Count > 1) ||250 (!string.Equals(provider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase)))251 {252 ErrorRecord error = new(253 new ArgumentException(254 string.Format(255 CultureInfo.InvariantCulture,256 CmsCommands.FilePathMustBeFileSystemPath,257 Path)),258 "FilePathMustBeFileSystemPath",259 ErrorCategory.ObjectNotFound,260 provider);261 ThrowTerminatingError(error);262 }263 264 _resolvedPath = resolvedPaths[0];265 }266 267 if (!string.IsNullOrEmpty(LiteralPath))268 {269 // Validate that the path exists270 SessionState.InvokeProvider.Item.Get(new string[] { LiteralPath }, false, true);271 _resolvedPath = LiteralPath;272 }273 }274 275 /// <summary>276 /// Processes records from the input pipeline.277 /// For each input object, the command gets the information278 /// about the protected message and exports the object.279 /// </summary>280 protected override void ProcessRecord()281 {282 if (string.Equals("ByContent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))283 {284 if (_contentBuffer.Length > 0)285 {286 _contentBuffer.Append(System.Environment.NewLine);287 }288 289 _contentBuffer.Append(Content);290 }291 }292 293 /// <summary>294 /// Gets the CMS Message object.295 /// </summary>296 protected override void EndProcessing()297 {298 string actualContent = null;299 300 // Read in the content301 if (string.Equals("ByContent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))302 {303 actualContent = _contentBuffer.ToString();304 }305 else306 {307 actualContent = System.IO.File.ReadAllText(_resolvedPath);308 }309 310 // Extract out the bytes and Base64 decode them311 byte[] contentBytes = CmsUtils.RemoveAsciiArmor(actualContent, CmsUtils.BEGIN_CMS_SIGIL, CmsUtils.END_CMS_SIGIL, out int _, out int _);312 if (contentBytes == null)313 {314 ErrorRecord error = new(315 new ArgumentException(CmsCommands.InputContainedNoEncryptedContent),316 "InputContainedNoEncryptedContent", ErrorCategory.ObjectNotFound, null);317 ThrowTerminatingError(error);318 }319 320 EnvelopedCms cms = new();321 cms.Decode(contentBytes);322 323 PSObject result = new(cms);324 List<object> recipients = new();325 foreach (RecipientInfo recipient in cms.RecipientInfos)326 {327 recipients.Add(recipient.RecipientIdentifier.Value);328 }329 330 result.Properties.Add(331 new PSNoteProperty("Recipients", recipients));332 result.Properties.Add(333 new PSNoteProperty("Content", actualContent));334 335 WriteObject(result);336 }337 }338 339 /// <summary>340 /// Defines the implementation of the 'Unprotect-CmsMessage' cmdlet.341 ///342 /// This cmdlet retrieves the clear text content of an encrypted CMS343 /// message.344 /// </summary>345 [Cmdlet(VerbsSecurity.Unprotect, "CmsMessage", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096701", DefaultParameterSetName = "ByWinEvent")]346 [OutputType(typeof(string))]347 public sealed class UnprotectCmsMessageCommand : PSCmdlet348 {349 /// <summary>350 /// Gets or sets the content of the CMS Message.351 /// </summary>352 [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "ByContent")]353 [AllowNull]354 [AllowEmptyString]355 public string Content356 {357 get;358 set;359 }360 361 private readonly StringBuilder _contentBuffer = new();362 363 /// <summary>364 /// Gets or sets the Windows Event Log Message with contents to be decrypted.365 /// </summary>366 [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = "ByWinEvent")]367 [PSTypeName("System.Diagnostics.Eventing.Reader.EventLogRecord")]368 public PSObject EventLogRecord369 {370 get;371 set;372 }373 374 /// <summary>375 /// Gets or sets the CMS Message by path.376 /// </summary>377 [Parameter(Position = 0, Mandatory = true, ParameterSetName = "ByPath")]378 public string Path379 {380 get;381 set;382 }383 384 /// <summary>385 /// Gets or sets the CMS Message by literal path.386 /// </summary>387 [Parameter(Position = 0, Mandatory = true, ParameterSetName = "ByLiteralPath")]388 public string LiteralPath389 {390 get;391 set;392 }393 394 private string _resolvedPath = null;395 396 /// <summary>397 /// Determines whether to include the decrypted content in its original context,398 /// rather than just output the decrypted content itself.399 /// </summary>400 [Parameter]401 public SwitchParameter IncludeContext402 {403 get;404 set;405 }406 407 /// <summary>408 /// Gets or sets the recipient of the CMS Message.409 /// </summary>410 [Parameter(Position = 1)]411 public CmsMessageRecipient[] To412 {413 get;414 set;415 }416 417 /// <summary>418 /// Validate / convert arguments.419 /// </summary>420 protected override void BeginProcessing()421 {422 // Validate Path423 if (!string.IsNullOrEmpty(Path))424 {425 ProviderInfo provider = null;426 Collection<string> resolvedPaths = GetResolvedProviderPathFromPSPath(Path, out provider);427 428 // Ensure the path is a single path from the file system provider429 if ((resolvedPaths.Count > 1) ||430 (!string.Equals(provider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase)))431 {432 ErrorRecord error = new(433 new ArgumentException(434 string.Format(435 CultureInfo.InvariantCulture,436 CmsCommands.FilePathMustBeFileSystemPath,437 Path)),438 "FilePathMustBeFileSystemPath",439 ErrorCategory.ObjectNotFound,440 provider);441 ThrowTerminatingError(error);442 }443 444 _resolvedPath = resolvedPaths[0];445 }446 447 if (!string.IsNullOrEmpty(LiteralPath))448 {449 // Validate that the path exists450 SessionState.InvokeProvider.Item.Get(new string[] { LiteralPath }, false, true);451 _resolvedPath = LiteralPath;452 }453 }454 455 /// <summary>456 /// Processes records from the input pipeline.457 /// For each input object, the command gets the information458 /// about the protected message and exports the object.459 /// </summary>460 protected override void ProcessRecord()461 {462 // If we're process by content, collect it.463 if (string.Equals("ByContent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))464 {465 if (_contentBuffer.Length > 0)466 {467 _contentBuffer.Append(System.Environment.NewLine);468 }469 470 _contentBuffer.Append(Content);471 }472 473 // If we're processing event log records, decrypt those inline.474 if (string.Equals("ByWinEvent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))475 {476 string actualContent = EventLogRecord.Properties["Message"].Value.ToString();477 string decrypted = Decrypt(actualContent);478 479 if (!IncludeContext)480 {481 WriteObject(decrypted);482 }483 else484 {485 EventLogRecord.Properties["Message"].Value = decrypted;486 WriteObject(EventLogRecord);487 }488 }489 }490 491 /// <summary>492 /// Processes records from the input pipeline.493 /// For each input object, the command gets the information494 /// about the protected message and exports the object.495 /// </summary>496 protected override void EndProcessing()497 {498 if (string.Equals("ByWinEvent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))499 {500 return;501 }502 503 string actualContent = null;504 505 // Read in the content506 if (string.Equals("ByContent", this.ParameterSetName, StringComparison.OrdinalIgnoreCase))507 {508 actualContent = _contentBuffer.ToString();509 }510 else511 {512 actualContent = System.IO.File.ReadAllText(_resolvedPath);513 }514 515 string decrypted = Decrypt(actualContent);516 WriteObject(decrypted);517 }518 519 private string Decrypt(string actualContent)520 {521 // Extract out the bytes and Base64 decode them522 int startIndex, endIndex;523 byte[] messageBytes = CmsUtils.RemoveAsciiArmor(actualContent, CmsUtils.BEGIN_CMS_SIGIL, CmsUtils.END_CMS_SIGIL, out startIndex, out endIndex);524 if ((messageBytes == null) && (!IncludeContext))525 {526 ErrorRecord error = new(527 new ArgumentException(528 string.Format(529 CultureInfo.InvariantCulture,530 CmsCommands.InputContainedNoEncryptedContentIncludeContext,531 "-IncludeContext")),532 "InputContainedNoEncryptedContentIncludeContext",533 ErrorCategory.ObjectNotFound,534 targetObject: null);535 ThrowTerminatingError(error);536 }537 538 // Capture the pre and post context, if there was any539 string preContext = null;540 string postContext = null;541 if (IncludeContext)542 {543 if (startIndex > -1)544 {545 preContext = actualContent.Substring(0, startIndex);546 }547 548 if (endIndex > -1)549 {550 postContext = actualContent.Substring(endIndex);551 }552 }553 554 EnvelopedCms cms = new();555 X509Certificate2Collection certificates = new();556 557 if ((To != null) && (To.Length > 0))558 {559 ErrorRecord error = null;560 561 foreach (CmsMessageRecipient recipient in To)562 {563 recipient.Resolve(this.SessionState, ResolutionPurpose.Decryption, out error);564 if (error != null)565 {566 ThrowTerminatingError(error);567 return null;568 }569 570 foreach (X509Certificate2 certificate in recipient.Certificates)571 {572 certificates.Add(certificate);573 }574 }575 }576 577 string resultString = actualContent;578 if (messageBytes != null)579 {580 cms.Decode(messageBytes);581 cms.Decrypt(certificates);582 583 resultString = System.Text.Encoding.UTF8.GetString(cms.ContentInfo.Content);584 }585 586 if (IncludeContext)587 {588 if (preContext != null)589 {590 resultString = preContext + resultString;591 }592 593 if (postContext != null)594 {595 resultString += postContext;596 }597 }598 599 return resultString;600 }601 }602}603 