MegaBites-AI/Windows-powershell
0308
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4#pragma warning disable 1634, 16915 6using System.Collections.Generic;7using System.Transactions;8using System.Management.Automation.Internal;9 10namespace System.Management.Automation11{12 /// <summary>13 /// The status of a PowerShell transaction.14 /// </summary>15 public enum PSTransactionStatus16 {17 /// <summary>18 /// The transaction has been rolled back.19 /// </summary>20 RolledBack = 0,21 22 /// <summary>23 /// The transaction has been committed.24 /// </summary>25 Committed = 1,26 27 /// <summary>28 /// The transaction is currently active.29 /// </summary>30 Active = 231 }32 33 /// <summary>34 /// Represents an active transaction.35 /// </summary>36 public sealed class PSTransaction : IDisposable37 {38 /// <summary>39 /// Initializes a new instance of the PSTransaction class.40 /// </summary>41 internal PSTransaction(RollbackSeverity rollbackPreference, TimeSpan timeout)42 {43 _transaction = new CommittableTransaction(timeout);44 RollbackPreference = rollbackPreference;45 _subscriberCount = 1;46 }47 48 /// <summary>49 /// Initializes a new instance of the PSTransaction class using a CommittableTransaction.50 /// </summary>51 internal PSTransaction(CommittableTransaction transaction, RollbackSeverity severity)52 {53 _transaction = transaction;54 RollbackPreference = severity;55 _subscriberCount = 1;56 }57 58 private CommittableTransaction _transaction;59 60 /// <summary>61 /// Gets the rollback preference for this transaction.62 /// </summary>63 public RollbackSeverity RollbackPreference { get; }64 65 /// <summary>66 /// Gets the number of subscribers to this transaction.67 /// </summary>68 public int SubscriberCount69 {70 get71 {72 // Verify the transaction hasn't been rolled back beneath us73 if (this.IsRolledBack)74 {75 this.SubscriberCount = 0;76 }77 78 return _subscriberCount;79 }80 81 set { _subscriberCount = value; }82 }83 84 private int _subscriberCount;85 86 /// <summary>87 /// Returns the status of this transaction.88 /// </summary>89 public PSTransactionStatus Status90 {91 get92 {93 if (IsRolledBack)94 {95 return PSTransactionStatus.RolledBack;96 }97 else if (IsCommitted)98 {99 return PSTransactionStatus.Committed;100 }101 else102 {103 return PSTransactionStatus.Active;104 }105 }106 }107 108 /// <summary>109 /// Activates the transaction held by this PSTransaction.110 /// </summary>111 internal void Activate()112 {113 Transaction.Current = _transaction;114 }115 116 /// <summary>117 /// Commits the transaction held by this PSTransaction.118 /// </summary>119 internal void Commit()120 {121 _transaction.Commit();122 IsCommitted = true;123 }124 125 /// <summary>126 /// Rolls back the transaction held by this PSTransaction.127 /// </summary>128 internal void Rollback()129 {130 _transaction.Rollback();131 _isRolledBack = true;132 }133 134 /// <summary>135 /// Determines whether this PSTransaction has been136 /// rolled back or not.137 /// </summary>138 internal bool IsRolledBack139 {140 get141 {142 // Check if it's been aborted underneath us143 if (144 (!_isRolledBack) &&145 (_transaction != null) &&146 (_transaction.TransactionInformation.Status == TransactionStatus.Aborted))147 {148 _isRolledBack = true;149 }150 151 return _isRolledBack;152 }153 154 set155 {156 _isRolledBack = value;157 }158 }159 160 private bool _isRolledBack = false;161 162 /// <summary>163 /// Determines whether this PSTransaction164 /// has been committed or not.165 /// </summary>166 internal bool IsCommitted { get; set; } = false;167 168 /// <summary>169 /// Destructor for the PSTransaction class.170 /// </summary>171 ~PSTransaction()172 {173 Dispose(false);174 }175 176 /// <summary>177 /// Disposes the PSTransaction object.178 /// </summary>179 public void Dispose()180 {181 Dispose(true);182 GC.SuppressFinalize(this);183 }184 185 /// <summary>186 /// Disposes the PSTransaction object, which disposes the187 /// underlying transaction.188 /// </summary>189 /// <param name="disposing">190 /// Whether to actually dispose the object.191 /// </param>192 public void Dispose(bool disposing)193 {194 if (disposing)195 {196 if (_transaction != null)197 {198 _transaction.Dispose();199 }200 }201 }202 }203 204 /// <summary>205 /// Supports the transaction management infrastructure for the PowerShell engine.206 /// </summary>207 public sealed class PSTransactionContext : IDisposable208 {209 /// <summary>210 /// Initializes a new instance of the PSTransactionManager class.211 /// </summary>212 internal PSTransactionContext(PSTransactionManager transactionManager)213 {214 _transactionManager = transactionManager;215 transactionManager.SetActive();216 }217 218 private PSTransactionManager _transactionManager;219 220 /// <summary>221 /// Destructor for the PSTransactionManager class.222 /// </summary>223 ~PSTransactionContext()224 {225 Dispose(false);226 }227 228 /// <summary>229 /// Disposes the PSTransactionContext object.230 /// </summary>231 public void Dispose()232 {233 Dispose(true);234 GC.SuppressFinalize(this);235 }236 237 /// <summary>238 /// Disposes the PSTransactionContext object, which resets the239 /// active PSTransaction.240 /// </summary>241 /// <param name="disposing">242 /// Whether to actually dispose the object.243 /// </param>244 private void Dispose(bool disposing)245 {246 if (disposing)247 {248 _transactionManager.ResetActive();249 }250 }251 }252 253 /// <summary>254 /// The severity of error that causes PowerShell to automatically255 /// rollback the transaction.256 /// </summary>257 public enum RollbackSeverity258 {259 /// <summary>260 /// Non-terminating errors or worse.261 /// </summary>262 Error,263 264 /// <summary>265 /// Terminating errors or worse.266 /// </summary>267 TerminatingError,268 269 /// <summary>270 /// Do not rollback the transaction on error.271 /// </summary>272 Never273 }274}275 276namespace System.Management.Automation.Internal277{278 /// <summary>279 /// Supports the transaction management infrastructure for the PowerShell engine.280 /// </summary>281 internal sealed class PSTransactionManager : IDisposable282 {283 /// <summary>284 /// Initializes a new instance of the PSTransactionManager class.285 /// </summary>286 internal PSTransactionManager()287 {288 _transactionStack = new Stack<PSTransaction>();289 _transactionStack.Push(null);290 }291 292 /// <summary>293 /// Called by engine APIs to ensure they are protected from294 /// ambient transactions.295 /// </summary>296 internal static IDisposable GetEngineProtectionScope()297 {298 if (s_engineProtectionEnabled && (Transaction.Current != null))299 {300 return new System.Transactions.TransactionScope(301 System.Transactions.TransactionScopeOption.Suppress);302 }303 else304 {305 return null;306 }307 }308 309 /// <summary>310 /// Called by the transaction manager to enable engine311 /// protection the first time a transaction is activated.312 /// Engine protection APIs remain protected from this point on.313 /// </summary>314 internal static void EnableEngineProtection()315 {316 s_engineProtectionEnabled = true;317 }318 319 private static bool s_engineProtectionEnabled = false;320 321 /// <summary>322 /// Gets the rollback preference for the active transaction.323 /// </summary>324 internal RollbackSeverity RollbackPreference325 {326 get327 {328 PSTransaction currentTransaction = _transactionStack.Peek();329 330 if (currentTransaction == null)331 {332 string error = TransactionStrings.NoTransactionActive;333 334 // This is not an expected condition, and is just protective335 // coding.336#pragma warning suppress 56503337 throw new InvalidOperationException(error);338 }339 340 return currentTransaction.RollbackPreference;341 }342 }343 344 /// <summary>345 /// Creates a new Transaction if none are active. Otherwise, increments346 /// the subscriber count for the active transaction.347 /// </summary>348 internal void CreateOrJoin()349 {350 CreateOrJoin(RollbackSeverity.Error, TimeSpan.FromMinutes(1));351 }352 353 /// <summary>354 /// Creates a new Transaction if none are active. Otherwise, increments355 /// the subscriber count for the active transaction.356 /// </summary>357 internal void CreateOrJoin(RollbackSeverity rollbackPreference, TimeSpan timeout)358 {359 PSTransaction currentTransaction = _transactionStack.Peek();360 361 // There is a transaction on the stack362 if (currentTransaction != null)363 {364 // If you are already in a transaction that has been aborted, or committed,365 // create it.366 if (currentTransaction.IsRolledBack || currentTransaction.IsCommitted)367 {368 // Clean up the "used" one369 _transactionStack.Pop().Dispose();370 371 // And add a new one to the stack372 _transactionStack.Push(new PSTransaction(rollbackPreference, timeout));373 }374 else375 {376 // This is a usable one. Add a subscriber to it.377 currentTransaction.SubscriberCount++;378 }379 }380 else381 {382 // Add a new transaction to the stack383 _transactionStack.Push(new PSTransaction(rollbackPreference, timeout));384 }385 }386 387 /// <summary>388 /// Creates a new Transaction that should be managed independently of389 /// any parent transactions.390 /// </summary>391 internal void CreateNew()392 {393 CreateNew(RollbackSeverity.Error, TimeSpan.FromMinutes(1));394 }395 396 /// <summary>397 /// Creates a new Transaction that should be managed independently of398 /// any parent transactions.399 /// </summary>400 internal void CreateNew(RollbackSeverity rollbackPreference, TimeSpan timeout)401 {402 _transactionStack.Push(new PSTransaction(rollbackPreference, timeout));403 }404 405 /// <summary>406 /// Completes the current transaction. If only one subscriber is active, this407 /// commits the transaction. Otherwise, it reduces the subscriber count by one.408 /// </summary>409 internal void Commit()410 {411 PSTransaction currentTransaction = _transactionStack.Peek();412 413 // Should not be able to commit a transaction that is not active414 if (currentTransaction == null)415 {416 string error = TransactionStrings.NoTransactionActiveForCommit;417 throw new InvalidOperationException(error);418 }419 420 // If you are already in a transaction that has been aborted421 if (currentTransaction.IsRolledBack)422 {423 string error = TransactionStrings.TransactionRolledBackForCommit;424 throw new TransactionAbortedException(error);425 }426 427 // If you are already in a transaction that has been committed428 if (currentTransaction.IsCommitted)429 {430 string error = TransactionStrings.CommittedTransactionForCommit;431 throw new InvalidOperationException(error);432 }433 434 if (currentTransaction.SubscriberCount == 1)435 {436 currentTransaction.Commit();437 currentTransaction.SubscriberCount = 0;438 }439 else440 {441 currentTransaction.SubscriberCount--;442 }443 444 // Now that we've committed, go back to the last available transaction445 while ((_transactionStack.Count > 2) &&446 (_transactionStack.Peek().IsRolledBack || _transactionStack.Peek().IsCommitted))447 {448 _transactionStack.Pop().Dispose();449 }450 }451 452 /// <summary>453 /// Aborts the current transaction, no matter how many subscribers are part of it.454 /// </summary>455 internal void Rollback()456 {457 Rollback(false);458 }459 460 /// <summary>461 /// Aborts the current transaction, no matter how many subscribers are part of it.462 /// </summary>463 internal void Rollback(bool suppressErrors)464 {465 PSTransaction currentTransaction = _transactionStack.Peek();466 467 // Should not be able to roll back a transaction that is not active468 if (currentTransaction == null)469 {470 string error = TransactionStrings.NoTransactionActiveForRollback;471 throw new InvalidOperationException(error);472 }473 474 // If you are already in a transaction that has been aborted475 if (currentTransaction.IsRolledBack)476 {477 if (!suppressErrors)478 {479 // Otherwise, you should not be able to roll it back.480 string error = TransactionStrings.TransactionRolledBackForRollback;481 throw new TransactionAbortedException(error);482 }483 }484 485 // See if they've already committed the transaction486 if (currentTransaction.IsCommitted)487 {488 if (!suppressErrors)489 {490 string error = TransactionStrings.CommittedTransactionForRollback;491 throw new InvalidOperationException(error);492 }493 }494 495 // Roll back the transaction if it hasn't been rolled back496 currentTransaction.SubscriberCount = 0;497 currentTransaction.Rollback();498 499 // Now that we've rolled back, go back to the last available transaction500 while ((_transactionStack.Count > 2) &&501 (_transactionStack.Peek().IsRolledBack || _transactionStack.Peek().IsCommitted))502 {503 _transactionStack.Pop().Dispose();504 }505 }506 507 /// <summary>508 /// Sets the base transaction; any transactions created thereafter will be nested to this instance.509 /// </summary>510 internal void SetBaseTransaction(CommittableTransaction transaction, RollbackSeverity severity)511 {512 if (this.HasTransaction)513 {514 throw new InvalidOperationException(TransactionStrings.BaseTransactionMustBeFirst);515 }516 517 PSTransaction currentTransaction = _transactionStack.Peek();518 519 // If there is a "used" transaction at the top of the stack, clean it up520 while (_transactionStack.Peek() != null &&521 (_transactionStack.Peek().IsRolledBack || _transactionStack.Peek().IsCommitted))522 {523 _transactionStack.Pop().Dispose();524 }525 526 _baseTransaction = new PSTransaction(transaction, severity);527 _transactionStack.Push(_baseTransaction);528 }529 530 /// <summary>531 /// Removes the transaction added by SetBaseTransaction.532 /// </summary>533 internal void ClearBaseTransaction()534 {535 if (_baseTransaction == null)536 {537 throw new InvalidOperationException(TransactionStrings.BaseTransactionNotSet);538 }539 540 if (_transactionStack.Peek() != _baseTransaction)541 {542 throw new InvalidOperationException(TransactionStrings.BaseTransactionNotActive);543 }544 545 _transactionStack.Pop().Dispose();546 _baseTransaction = null;547 }548 549 private Stack<PSTransaction> _transactionStack;550 private PSTransaction _baseTransaction;551 552 /// <summary>553 /// Returns the current engine transaction.554 /// </summary>555 internal PSTransaction GetCurrent()556 {557 return _transactionStack.Peek();558 }559 560 /// <summary>561 /// Activates the current transaction, both in the engine, and in the Ambient.562 /// </summary>563 internal void SetActive()564 {565 PSTransactionManager.EnableEngineProtection();566 567 PSTransaction currentTransaction = _transactionStack.Peek();568 569 // Should not be able to activate a transaction that is not active570 if (currentTransaction == null)571 {572 string error = TransactionStrings.NoTransactionForActivation;573 throw new InvalidOperationException(error);574 }575 576 // If you are already in a transaction that has been aborted, you should577 // not be able to activate it.578 if (currentTransaction.IsRolledBack)579 {580 string error = TransactionStrings.NoTransactionForActivationBecauseRollback;581 throw new TransactionAbortedException(error);582 }583 584 _previousActiveTransaction = Transaction.Current;585 currentTransaction.Activate();586 }587 588 private Transaction _previousActiveTransaction;589 590 /// <summary>591 /// Deactivates the current transaction in the engine, and restores the592 /// ambient transaction.593 /// </summary>594 internal void ResetActive()595 {596 // Even if you are in a transaction that has been aborted, you597 // should still be able to restore the current transaction.598 599 Transaction.Current = _previousActiveTransaction;600 _previousActiveTransaction = null;601 }602 603 /// <summary>604 /// Determines if you have a transaction that you can set active and work on.605 /// </summary>606 internal bool HasTransaction607 {608 get609 {610 PSTransaction currentTransaction = _transactionStack.Peek();611 612 if ((currentTransaction != null) &&613 (!currentTransaction.IsCommitted) &&614 (!currentTransaction.IsRolledBack))615 {616 return true;617 }618 else619 {620 return false;621 }622 }623 }624 625 /// <summary>626 /// Determines if the last transaction has been committed.627 /// </summary>628 internal bool IsLastTransactionCommitted629 {630 get631 {632 PSTransaction currentTransaction = _transactionStack.Peek();633 634 if (currentTransaction != null)635 {636 return currentTransaction.IsCommitted;637 }638 else639 {640 return false;641 }642 }643 }644 645 /// <summary>646 /// Determines if the last transaction has been rolled back.647 /// </summary>648 internal bool IsLastTransactionRolledBack649 {650 get651 {652 PSTransaction currentTransaction = _transactionStack.Peek();653 654 if (currentTransaction != null)655 {656 return currentTransaction.IsRolledBack;657 }658 else659 {660 return false;661 }662 }663 }664 665 /// <summary>666 /// Destructor for the PSTransactionManager class.667 /// </summary>668 ~PSTransactionManager()669 {670 Dispose(false);671 }672 673 /// <summary>674 /// Disposes the PSTransactionManager object.675 /// </summary>676 public void Dispose()677 {678 Dispose(true);679 GC.SuppressFinalize(this);680 }681 682 /// <summary>683 /// Disposes the PSTransactionContext object, which resets the684 /// active PSTransaction.685 /// </summary>686 /// <param name="disposing">687 /// Whether to actually dispose the object.688 /// </param>689 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "baseTransaction", Justification = "baseTransaction should not be disposed since we do not own it - it belongs to the caller")]690 public void Dispose(bool disposing)691 {692 if (disposing)693 {694 ResetActive();695 696 while (_transactionStack.Peek() != null)697 {698 PSTransaction currentTransaction = _transactionStack.Pop();699 700 if (currentTransaction != _baseTransaction)701 {702 currentTransaction.Dispose();703 }704 }705 }706 }707 }708}709 