13
13
using System . Runtime . InteropServices ;
14
14
using System . Text ;
15
15
using System . Threading ;
16
+ using Microsoft . PowerShell . Commands ;
16
17
using Microsoft . PowerShell . Internal ;
17
18
18
19
[ module: SuppressMessage ( "Microsoft.Design" , "CA1014:MarkAssembliesWithClsCompliant" ) ]
@@ -25,13 +26,14 @@ class ExitException : Exception { }
25
26
26
27
public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
27
28
{
28
- private static readonly PSConsoleReadLine _singleton ;
29
+ private static readonly PSConsoleReadLine _singleton = new PSConsoleReadLine ( ) ;
30
+
29
31
private bool _delayedOneTimeInitCompleted ;
30
32
31
33
private IPSConsoleReadLineMockableMethods _mockableMethods ;
32
34
33
35
private EngineIntrinsics _engineIntrinsics ;
34
- private static readonly GCHandle _breakHandlerGcHandle ;
36
+ private static GCHandle _breakHandlerGcHandle ;
35
37
private Thread _readKeyThread ;
36
38
private AutoResetEvent _readKeyWaitHandle ;
37
39
private AutoResetEvent _keyReadWaitHandle ;
@@ -51,9 +53,10 @@ public partial class PSConsoleReadLine : IPSConsoleReadLineMockableMethods
51
53
private bool _inputAccepted ;
52
54
private readonly Queue < ConsoleKeyInfo > _queuedKeys ;
53
55
private Stopwatch _lastRenderTime ;
56
+ private static Stopwatch _readkeyStopwatch = new Stopwatch ( ) ;
54
57
55
58
// Save a fixed # of keys so we can reconstruct a repro after a crash
56
- private readonly static HistoryQueue < ConsoleKeyInfo > _lastNKeys ;
59
+ private readonly static HistoryQueue < ConsoleKeyInfo > _lastNKeys = new HistoryQueue < ConsoleKeyInfo > ( 200 ) ;
57
60
58
61
// Tokens etc.
59
62
private Token [ ] _tokens ;
@@ -74,32 +77,41 @@ bool IPSConsoleReadLineMockableMethods.KeyAvailable()
74
77
return Console . KeyAvailable ;
75
78
}
76
79
80
+ bool IPSConsoleReadLineMockableMethods . RunspaceIsRemote ( Runspace runspace )
81
+ {
82
+ return runspace != null && runspace . ConnectionInfo != null ;
83
+ }
84
+
85
+ private void ReadOneOrMoreKeys ( )
86
+ {
87
+ _readkeyStopwatch . Restart ( ) ;
88
+ while ( _mockableMethods . KeyAvailable ( ) )
89
+ {
90
+ _queuedKeys . Enqueue ( _mockableMethods . ReadKey ( ) ) ;
91
+ if ( _readkeyStopwatch . ElapsedMilliseconds > 2 )
92
+ {
93
+ // Don't spend too long in this loop if there are lots of queued keys
94
+ break ;
95
+ }
96
+ }
97
+
98
+ if ( _queuedKeys . Count == 0 )
99
+ {
100
+ var key = _mockableMethods . ReadKey ( ) ;
101
+ _queuedKeys . Enqueue ( key ) ;
102
+ }
103
+ }
104
+
77
105
private void ReadKeyThreadProc ( )
78
106
{
79
- var stopwatch = new Stopwatch ( ) ;
80
107
while ( true )
81
108
{
82
109
// Wait until ReadKey tells us to read a key (or it's time to exit).
83
110
int handleId = WaitHandle . WaitAny ( _singleton . _threadProcWaitHandles ) ;
84
111
if ( handleId == 1 ) // It was the _closingWaitHandle that was signaled.
85
112
break ;
86
113
87
- stopwatch . Restart ( ) ;
88
- while ( _mockableMethods . KeyAvailable ( ) )
89
- {
90
- _queuedKeys . Enqueue ( _mockableMethods . ReadKey ( ) ) ;
91
- if ( stopwatch . ElapsedMilliseconds > 2 )
92
- {
93
- // Don't spend too long in this loop if there are lots of queued keys
94
- break ;
95
- }
96
- }
97
-
98
- if ( _queuedKeys . Count == 0 )
99
- {
100
- var key = _mockableMethods . ReadKey ( ) ;
101
- _queuedKeys . Enqueue ( key ) ;
102
- }
114
+ ReadOneOrMoreKeys ( ) ;
103
115
104
116
// One or more keys were read - let ReadKey know we're done.
105
117
_keyReadWaitHandle . Set ( ) ;
@@ -207,6 +219,7 @@ private static ConsoleKeyInfo ReadKey()
207
219
208
220
throw new OperationCanceledException ( ) ;
209
221
}
222
+
210
223
var key = _singleton . _queuedKeys . Dequeue ( ) ;
211
224
return key ;
212
225
}
@@ -457,38 +470,6 @@ void ProcessOneKey(ConsoleKeyInfo key, Dictionary<ConsoleKeyInfo, KeyHandler> di
457
470
}
458
471
}
459
472
460
- static PSConsoleReadLine ( )
461
- {
462
- _singleton = new PSConsoleReadLine ( ) ;
463
-
464
- _breakHandlerGcHandle = GCHandle . Alloc ( new BreakHandler ( _singleton . BreakHandler ) ) ;
465
- NativeMethods . SetConsoleCtrlHandler ( ( BreakHandler ) _breakHandlerGcHandle . Target , true ) ;
466
- _singleton . _readKeyWaitHandle = new AutoResetEvent ( false ) ;
467
- _singleton . _keyReadWaitHandle = new AutoResetEvent ( false ) ;
468
- _singleton . _closingWaitHandle = new ManualResetEvent ( false ) ;
469
- _singleton . _requestKeyWaitHandles = new WaitHandle [ ] { _singleton . _keyReadWaitHandle , _singleton . _closingWaitHandle } ;
470
- _singleton . _threadProcWaitHandles = new WaitHandle [ ] { _singleton . _readKeyWaitHandle , _singleton . _closingWaitHandle } ;
471
-
472
- // This is only used for post-mortem debugging - 200 keys should be enough to reconstruct most command lines.
473
- _lastNKeys = new HistoryQueue < ConsoleKeyInfo > ( 200 ) ;
474
-
475
- // This is for a "being hosted in an alternate appdomain scenario" (the
476
- // DomainUnload event is not raised for the default appdomain). It allows us
477
- // to exit cleanly when the appdomain is unloaded but the process is not going
478
- // away.
479
- if ( ! AppDomain . CurrentDomain . IsDefaultAppDomain ( ) )
480
- {
481
- AppDomain . CurrentDomain . DomainUnload += ( x , y ) =>
482
- {
483
- _singleton . _closingWaitHandle . Set ( ) ;
484
- _singleton . _readKeyThread . Join ( ) ; // may need to wait for history to be written
485
- } ;
486
- }
487
-
488
- _singleton . _readKeyThread = new Thread ( _singleton . ReadKeyThreadProc ) { IsBackground = true } ;
489
- _singleton . _readKeyThread . Start ( ) ;
490
- }
491
-
492
473
private PSConsoleReadLine ( )
493
474
{
494
475
_mockableMethods = this ;
@@ -501,19 +482,23 @@ private PSConsoleReadLine()
501
482
_queuedKeys = new Queue < ConsoleKeyInfo > ( ) ;
502
483
503
484
string hostName = null ;
504
- try
485
+ // This works mostly by luck - we're not doing anything to guarantee the constructor for our
486
+ // singleton is called on a thread with a runspace, but it is happening by coincidence.
487
+ using ( var ps = System . Management . Automation . PowerShell . Create ( RunspaceMode . CurrentRunspace ) )
505
488
{
506
- var ps = System . Management . Automation . PowerShell . Create ( RunspaceMode . CurrentRunspace )
507
- . AddCommand ( "Get-Variable" ) . AddParameter ( "Name" , "host" ) . AddParameter ( "ValueOnly" ) ;
508
- var results = ps . Invoke ( ) ;
509
- dynamic host = results . Count == 1 ? results [ 0 ] : null ;
510
- if ( host != null )
489
+ try
490
+ {
491
+ ps . AddCommand ( "Get-Variable" ) . AddParameter ( "Name" , "host" ) . AddParameter ( "ValueOnly" ) ;
492
+ var results = ps . Invoke ( ) ;
493
+ dynamic host = results . Count == 1 ? results [ 0 ] : null ;
494
+ if ( host != null )
495
+ {
496
+ hostName = host . Name as string ;
497
+ }
498
+ }
499
+ catch
511
500
{
512
- hostName = host . Name as string ;
513
501
}
514
- }
515
- catch
516
- {
517
502
}
518
503
if ( hostName == null )
519
504
{
@@ -522,15 +507,17 @@ private PSConsoleReadLine()
522
507
_options = new PSConsoleReadlineOptions ( hostName ) ;
523
508
}
524
509
525
- private void Initialize ( Runspace remoteRunspace , EngineIntrinsics engineIntrinsics )
510
+ private void Initialize ( Runspace runspace , EngineIntrinsics engineIntrinsics )
526
511
{
512
+ _engineIntrinsics = engineIntrinsics ;
513
+ _runspace = runspace ;
514
+
527
515
if ( ! _delayedOneTimeInitCompleted )
528
516
{
529
517
DelayedOneTimeInitialize ( ) ;
530
518
_delayedOneTimeInitCompleted = true ;
531
519
}
532
520
533
- _engineIntrinsics = engineIntrinsics ;
534
521
_buffer . Clear ( ) ;
535
522
_edits = new List < EditItem > ( ) ;
536
523
_undoEditIndex = 0 ;
@@ -553,7 +540,6 @@ private void Initialize(Runspace remoteRunspace, EngineIntrinsics engineIntrinsi
553
540
_yankLastArgCommandCount = 0 ;
554
541
_tabCommandCount = 0 ;
555
542
_visualSelectionCommandCount = 0 ;
556
- _remoteRunspace = remoteRunspace ;
557
543
_statusIsErrorMessage = false ;
558
544
559
545
_consoleBuffer = ReadBufferLines ( _initialY , 1 + Options . ExtraPromptLineCount ) ;
@@ -595,15 +581,68 @@ private void DelayedOneTimeInitialize()
595
581
// specifies a custom history save file, we don't want to try reading
596
582
// from the default one.
597
583
584
+ var historyCountVar = _engineIntrinsics . SessionState . PSVariable . Get ( "MaximumHistoryCount" ) ;
585
+ if ( historyCountVar != null && historyCountVar . Value is int )
586
+ {
587
+ _options . MaximumHistoryCount = ( int ) historyCountVar . Value ;
588
+ }
589
+
598
590
_historyFileMutex = new Mutex ( false , GetHistorySaveFileMutexName ( ) ) ;
599
591
600
592
_history = new HistoryQueue < HistoryItem > ( Options . MaximumHistoryCount ) ;
601
593
_currentHistoryIndex = 0 ;
602
594
603
- ReadHistoryFile ( ) ;
595
+ bool readHistoryFile = true ;
596
+ try
597
+ {
598
+ if ( _options . HistorySaveStyle == HistorySaveStyle . SaveNothing && Runspace . DefaultRunspace != null )
599
+ {
600
+ using ( var ps = System . Management . Automation . PowerShell . Create ( RunspaceMode . CurrentRunspace ) )
601
+ {
602
+ ps . AddCommand ( "Microsoft.PowerShell.Core\\ Get-History" ) ;
603
+ foreach ( var historyInfo in ps . Invoke < HistoryInfo > ( ) )
604
+ {
605
+ AddToHistory ( historyInfo . CommandLine ) ;
606
+ }
607
+ readHistoryFile = false ;
608
+ }
609
+ }
610
+ }
611
+ catch
612
+ {
613
+ }
614
+
615
+ if ( readHistoryFile )
616
+ {
617
+ ReadHistoryFile ( ) ;
618
+ }
604
619
605
620
_killIndex = - 1 ; // So first add indexes 0.
606
621
_killRing = new List < string > ( Options . MaximumKillRingCount ) ;
622
+
623
+ _breakHandlerGcHandle = GCHandle . Alloc ( new BreakHandler ( _singleton . BreakHandler ) ) ;
624
+ NativeMethods . SetConsoleCtrlHandler ( ( BreakHandler ) _breakHandlerGcHandle . Target , true ) ;
625
+ _singleton . _readKeyWaitHandle = new AutoResetEvent ( false ) ;
626
+ _singleton . _keyReadWaitHandle = new AutoResetEvent ( false ) ;
627
+ _singleton . _closingWaitHandle = new ManualResetEvent ( false ) ;
628
+ _singleton . _requestKeyWaitHandles = new WaitHandle [ ] { _singleton . _keyReadWaitHandle , _singleton . _closingWaitHandle } ;
629
+ _singleton . _threadProcWaitHandles = new WaitHandle [ ] { _singleton . _readKeyWaitHandle , _singleton . _closingWaitHandle } ;
630
+
631
+ // This is for a "being hosted in an alternate appdomain scenario" (the
632
+ // DomainUnload event is not raised for the default appdomain). It allows us
633
+ // to exit cleanly when the appdomain is unloaded but the process is not going
634
+ // away.
635
+ if ( ! AppDomain . CurrentDomain . IsDefaultAppDomain ( ) )
636
+ {
637
+ AppDomain . CurrentDomain . DomainUnload += ( x , y ) =>
638
+ {
639
+ _singleton . _closingWaitHandle . Set ( ) ;
640
+ _singleton . _readKeyThread . Join ( ) ; // may need to wait for history to be written
641
+ } ;
642
+ }
643
+
644
+ _singleton . _readKeyThread = new Thread ( _singleton . ReadKeyThreadProc ) { IsBackground = true } ;
645
+ _singleton . _readKeyThread . Start ( ) ;
607
646
}
608
647
609
648
[ SuppressMessage ( "Microsoft.Design" , "CA1026:DefaultParametersShouldNotBeUsed" ) ]
0 commit comments