bin/projects/dbatools/dbatools/TabExpansion/TabExpansionHost.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading;

namespace Sqlcollaborative.Dbatools.TabExpansion
{
    /// <summary>
    /// Class that handles the static fields supporting the dbatools TabExpansion implementation
    /// </summary>
    public static class TabExpansionHost
    {
        #region State information
        /// <summary>
        /// Field containing the scripts that were registered.
        /// </summary>
        public static ConcurrentDictionary<string, ScriptContainer> Scripts = new ConcurrentDictionary<string, ScriptContainer>();

        /// <summary>
        /// The cache used by scripts utilizing TabExpansionPlusPlus in dbatools
        /// </summary>
        public static Hashtable Cache = new Hashtable();

        /// <summary>
        /// List of instances and when they were last accessed
        /// </summary>
        public static ConcurrentDictionary<string, InstanceAccess> InstanceAccess = new ConcurrentDictionary<string, InstanceAccess>();

        /// <summary>
        /// Scripts that build the cache and are suitable for synchronous execution
        /// </summary>
        public static List<ScriptBlock> TeppGatherScriptsFast = new List<ScriptBlock>();

        /// <summary>
        /// Scripts that build the cache and are not suitable for synchronous execution
        /// </summary>
        public static List<ScriptBlock> TeppGatherScriptsSlow = new List<ScriptBlock>();
        #endregion State information

        #region Utility methods
        /// <summary>
        /// Registers a new instance or updates an already existing one. Should only be called from Connect-SqlInstance and Connect-DbaSqlServer
        /// </summary>
        /// <param name="InstanceName">Name of the instance connected to</param>
        /// <param name="Connection">To connection object containing the relevant information for accessing the instance</param>
        /// <param name="IsSysAdmin">Whether the account connecting to the instnace has SA privileges</param>
        public static void SetInstance(string InstanceName, object Connection, bool IsSysAdmin)
        {
            string tempName = InstanceName.ToLower();

            if (!InstanceAccess.ContainsKey(tempName))
            {
                InstanceAccess tempAccess = new InstanceAccess();
                tempAccess.InstanceName = tempName;
                tempAccess.LastAccess = DateTime.Now;
                tempAccess.ConnectionObject = Connection;
                tempAccess.IsSysAdmin = IsSysAdmin;
                InstanceAccess[tempName] = tempAccess;
            }
            else
            {
                InstanceAccess[tempName].LastAccess = DateTime.Now;

                if (IsSysAdmin & !InstanceAccess[tempName].IsSysAdmin)
                {
                    InstanceAccess[tempName].ConnectionObject = Connection;
                    InstanceAccess[tempName].IsSysAdmin = IsSysAdmin;
                }
            }
        }
        #endregion Utility methods

        #region Configuration
        /// <summary>
        /// Whether TEPP in its entirety is disabled
        /// </summary>
        public static bool TeppDisabled = false;

        /// <summary>
        /// Whether asynchronous TEPP updating should be disabled
        /// </summary>
        public static bool TeppAsyncDisabled = false;

        /// <summary>
        /// Whether synchronous TEPP updating should be disabled
        /// </summary>
        public static bool TeppSyncDisabled = true;

        /// <summary>
        /// The interval in which asynchronous TEPP cache updates are performed
        /// </summary>
        public static TimeSpan TeppUpdateInterval = new TimeSpan(0, 3, 0);

        /// <summary>
        /// After this timespan of no requests to a server, the updates to its cache are disabled.
        /// </summary>
        public static TimeSpan TeppUpdateTimeout = new TimeSpan(0, 30, 0);
        #endregion Configuration

        #region Updater
        private static ScriptBlock TeppUpdateScript;

        private static PowerShell TeppUdater;

        /// <summary>
        /// Setting this to true should cause the script running in the runspace to selfterminate, allowing a graceful selftermination.
        /// </summary>
        public static bool TeppUdaterStopper
        {
            get { return _TeppUdaterStopper; }
        }
        private static bool _TeppUdaterStopper;

        /// <summary>
        /// Set the script to use as part of the TEPP updater
        /// </summary>
        /// <param name="Script">The script to use</param>
        public static void SetScript(ScriptBlock Script)
        {
            TeppUpdateScript = Script;
        }

        /// <summary>
        /// Starts the TEPP Updater.
        /// </summary>
        public static void Start()
        {
            if (TeppUdater == null)
            {
                _TeppUdaterStopper = false;
                TeppUdater = PowerShell.Create();
                TeppUdater.AddScript(TeppUpdateScript.ToString());
                TeppUdater.BeginInvoke();
            }
        }

        /// <summary>
        /// Gracefully stops the TEPP Updater
        /// </summary>
        public static void Stop()
        {
            _TeppUdaterStopper = true;

            int i = 0;

            // Wait up to 30 seconds for the running script to notice and kill itself
            while ((TeppUdater.Runspace.RunspaceAvailability != RunspaceAvailability.Available) && (i < 300))
            {
                i++;
                Thread.Sleep(100);
            }

            Kill();
        }

        /// <summary>
        /// Very ungracefully kills the TEPP Updater. Use only in the most dire emergency.
        /// </summary>
        public static void Kill()
        {
            if (TeppUdater != null)
            {
                TeppUdater.Runspace.Close();
                TeppUdater.Dispose();
                TeppUdater = null;
            }
        }
        #endregion Updater
    }
}