bin/projects/dbatools/dbatools/Commands/WriteMessageCommand.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
using Sqlcollaborative.Dbatools.Message;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Text.RegularExpressions;

namespace Sqlcollaborative.Dbatools.Commands
{
    /// <summary>
    /// Implements the Write-Message command, performing message handling and loggin
    /// </summary>
    [Cmdlet("Write", "Message")]
    public class WriteMessageCommand : PSCmdlet
    {
        #region Parameters
        /// <summary>
        /// This parameter represents the verbosity of the message. The lower the number, the more important it is for a human user to read the message.
        /// By default, the levels are distributed like this:
        /// - 1-3 Direct verbose output to the user (using Write-Host)
        /// - 4-6 Output only visible when requesting extra verbosity (using Write-Verbose)
        /// - 1-9 Debugging information, written using Write-Debug
        /// 
        /// In addition, it is possible to select the level "Warning" which moves the message out of the configurable range:
        /// The user will always be shown this message, unless he silences the entire verbosity.
        /// 
        /// Possible levels:
        /// Critical (1), Important / Output / Host (2), Significant (3), VeryVerbose (4), Verbose (5), SomewhatVerbose (6), System (7), Debug (8), InternalComment (9), Warning (666)
        /// Either one of the strings or its respective number will do as input.
        /// </summary>
        [Parameter()]
        public MessageLevel Level = MessageLevel.Verbose;

        /// <summary>
        /// The message to write/log. The function name and timestamp will automatically be prepended.
        /// </summary>
        [Parameter(Mandatory = true, Position = 0)]
        [AllowEmptyString]
        [AllowNull]
        public string Message;

        /// <summary>
        /// Tags to add to the message written.
        /// This allows filtering and grouping by category of message, targeting specific messages.
        /// </summary>
        [Parameter()]
        public string[] Tag;

        /// <summary>
        /// The name of the calling function.
        /// Will be automatically set, but can be overridden when necessary.
        /// </summary>
        [Parameter()]
        public string FunctionName;

        /// <summary>
        /// The name of the module, the calling function is part of.
        /// Will be automatically set, but can be overridden when necessary.
        /// </summary>
        [Parameter()]
        public string ModuleName;

        /// <summary>
        /// The file in which Write-Message was called.
        /// Will be automatically set, but can be overridden when necessary.
        /// </summary>
        [Parameter()]
        public string File;

        /// <summary>
        /// The line on which Write-Message was called.
        /// Will be automatically set, but can be overridden when necessary.
        /// </summary>
        [Parameter()]
        public int Line;

        /// <summary>
        /// If an error record should be noted with the message, add the full record here.
        /// Especially designed for use with Warning-mode, it can legally be used in either mode.
        /// The error will be added to the $Error variable and enqued in the logging/debugging system.
        /// </summary>
        [Parameter()]
        public ErrorRecord[] ErrorRecord;

        /// <summary>
        /// Allows specifying an inner exception as input object. This will be passed on to the logging and used for messages.
        /// When specifying both ErrorRecord AND Exception, Exception wins, but ErrorRecord is still used for record metadata.
        /// </summary>
        [Parameter()]
        public Exception Exception;

        /// <summary>
        /// Setting this parameter will cause this function to write the message only once per session.
        /// The string passed here and the calling function's name are used to create a unique ID, which is then used to register the action in the configuration system.         /// Thus will the lockout only be written if called once and not burden the system unduly. /// This lockout will be written as a hidden value, to see it use Get-DbaConfig -Force. /// </summary> [Parameter()] public string Once; /// <summary> /// Disables automatic appending of exception messages.         /// Use in cases where you already have a speaking message interpretation and do not need the original message. /// </summary> [Parameter()] public SwitchParameter OverrideExceptionMessage; /// <summary> /// Add the object the message is all about, in order to simplify debugging / troubleshooting.         /// For example, when calling this from a function targeting a remote computer, the computername could be specified here, allowing all messages to easily be correlated to the object processed. /// </summary> [Parameter()] public object Target; /// <summary> /// This parameters disables user-friendly warnings and enables the throwing of exceptions.         /// This is less user friendly, but allows catching exceptions in calling scripts. /// </summary> [Parameter()] public bool EnableException; /// <summary> /// Enables breakpoints on the current message. By default, setting '-Debug' will NOT cause an interrupt on the current position. /// </summary> [Parameter()] public SwitchParameter Breakpoint; #endregion Parameters #region Private fields /// <summary> /// The start time of the cmdlet /// </summary> private DateTime _timestamp; /// <summary> /// Whether this cmdlet is run in silent mode /// </summary> private bool _silent = false; /// <summary> /// Whether this cmdlet was called by Stop-Function /// </summary> private bool _fromStopFunction = false; /// <summary> /// The current callstack /// </summary> private IEnumerable<CallStackFrame> _callStack = null; /// <summary> /// How many items exist on the callstack /// </summary> private int _stackDepth; /// <summary> /// The message to write /// </summary> private string _message; /// <summary> /// The message simplified without timestamps. Used for logging. /// </summary> private string _messageSimple; /// <summary> /// The message to write in color /// </summary> private string _messageColor; /// <summary> /// Non-colored version of developermode /// </summary> private string _messageDeveloper; /// <summary> /// Colored version of developermode /// </summary> private string _messageDeveloperColor; /// <summary> /// Scriptblock that writes the host messages /// </summary> private static string _writeHostScript = @" param ( $string ) if ([Sqlcollaborative.Dbatools.Message.MessageHost]::DeveloperMode) { Write-HostColor -String $string -DefaultColor ([Sqlcollaborative.Dbatools.Message.MessageHost]::DeveloperColor) -ErrorAction Ignore } else { Write-HostColor -String $string -DefaultColor ([Sqlcollaborative.Dbatools.Message.MessageHost]::InfoColor) -ErrorAction Ignore } "; /// <summary> /// List of tags to process /// </summary> private List<string> _Tags = new List<string>(); /// <summary> /// Whether debug mode is enabled /// </summary> private bool _isDebug; #endregion Private fields #region Private properties /// <summary> /// The input message with the error content included if desired /// </summary> private string _errorQualifiedMessage { get { if (ErrorRecord == null) return Message; if (ErrorRecord.Length == 0) return Message; if (OverrideExceptionMessage.ToBool()) return Message; if (Regex.IsMatch(Message, Regex.Escape(ErrorRecord[0].Exception.Message))) return Message; return String.Format("{0} | {1}", Message, ErrorRecord[0].Exception.Message); } } /// <summary> /// The final message to use for internal logging /// </summary> private string _MessageSystem { get { return GetMessageSimple(); } } /// <summary> /// The final message to use for writing to streams, such as verbose or warning /// </summary> private string _MessageStreams { get { if (MessageHost.DeveloperMode) return GetMessageDeveloper(); else return GetMessage(); } } /// <summary> /// The final message to use for host messages (write using Write-HostColor) /// </summary> private string _MessageHost { get { if (MessageHost.DeveloperMode) return GetMessageDeveloperColor(); else return GetMessageColor(); } } /// <summary> /// Provide breadcrumb queue of the callstack /// </summary> private string _BreadCrumbsString { get { string crumbs = String.Join(" > ", _callStack.Select(name => name.FunctionName).Where(name => name != "Write-Message" && name != "Stop-Function" && name != "<ScriptBlock>").Reverse().ToList()); if (crumbs.EndsWith(FunctionName)) return String.Format("[{0}]\n ", crumbs); return String.Format("[{0}] [{1}]\n ", crumbs, FunctionName); } } /// <summary> /// Provide a breadcrumb queue of the callstack in color tags /// </summary> private string _BreadCrumbsStringColored { get { string crumbs = String.Join("</c> > <c='sub'>", _callStack.Select(name => name.FunctionName).Where(name => name != "Write-Message" && name != "Stop-Function" && name != "<ScriptBlock>").Reverse().ToList()); if (crumbs.EndsWith(FunctionName)) return String.Format("[<c='sub'>{0}</c>]\n ", crumbs); return String.Format("[<c='sub'>{0}</c>] [<c='sub'>{1}</c>]\n ", crumbs, FunctionName); } } #endregion Private properties #region Cmdlet Implementation /// <summary> /// Processes the begin phase of the cmdlet /// </summary> protected override void BeginProcessing() { _timestamp = DateTime.Now; #region Resolving Meta Information _callStack = Utility.UtilityHost.Callstack; CallStackFrame callerFrame = null; if (_callStack.Count() > 0) callerFrame = _callStack.First(); _stackDepth = _callStack.Count(); if (callerFrame != null) { if (String.IsNullOrEmpty(FunctionName)) { if (callerFrame.InvocationInfo == null) FunctionName = callerFrame.FunctionName; else if (callerFrame.InvocationInfo.MyCommand == null) FunctionName = callerFrame.InvocationInfo.InvocationName; else if (callerFrame.InvocationInfo.MyCommand.Name != "") FunctionName = callerFrame.InvocationInfo.MyCommand.Name; else FunctionName = callerFrame.FunctionName; } if (String.IsNullOrEmpty(ModuleName)) if ((callerFrame.InvocationInfo != null) && (callerFrame.InvocationInfo.MyCommand != null)) ModuleName = callerFrame.InvocationInfo.MyCommand.ModuleName; if (String.IsNullOrEmpty(File)) File = callerFrame.Position.File; if (Line <= 0) Line = callerFrame.Position.EndLineNumber; if (callerFrame.FunctionName == "Stop-Function") _fromStopFunction = true; } if (String.IsNullOrEmpty(FunctionName)) FunctionName = "<Unknown>"; if (String.IsNullOrEmpty(ModuleName)) ModuleName = "<Unknown>"; if (MessageHost.DisableVerbosity) _silent = true; if (Tag != null) foreach (string item in Tag) _Tags.Add(item); _isDebug = (_callStack.Count() > 1) && _callStack.ElementAt(_callStack.Count() - 2).InvocationInfo.BoundParameters.ContainsKey("Debug"); #endregion Resolving Meta Information } /// <summary> /// Processes the process phase of the cmdlet /// </summary> protected override void ProcessRecord() { #region Perform Transforms if ((!_fromStopFunction) && (Target != null)) Target = ResolveTarget(Target); if (!_fromStopFunction) { if (Exception != null) Exception = ResolveException(Exception); else if (ErrorRecord != null) { Exception tempException = null; for (int n = 0; n < ErrorRecord.Length; n++) { // If both Exception and ErrorRecord are specified, override the first error record's exception.
                        if ((n == 0) && (Exception != null))
                            tempException = Exception;
                        else
                            tempException = ResolveException(ErrorRecord[n].Exception);
                        if (tempException != ErrorRecord[n].Exception)
                            ErrorRecord[n] = new ErrorRecord(tempException, ErrorRecord[n].FullyQualifiedErrorId, ErrorRecord[n].CategoryInfo.Category, ErrorRecord[n].TargetObject);
                    }
                }
            }

            if (Level != MessageLevel.Warning)
                Level = ResolveLevel(Level);
            #endregion Perform Transforms

            #region Exception Integration
            /*
                While conclusive error handling must happen after message handling,
                in order to integrate the exception message into the actual message,
                it becomes necessary to first integrate the exception and error record parameters into a uniform view
    
                Note: Stop-Function never specifies this parameter, thus it is not necessary to check,
                whether this function was called from Stop-Function.
             */
            if ((ErrorRecord == null) && (Exception != null))
            {
                ErrorRecord = new ErrorRecord[1];
                ErrorRecord[0] = new ErrorRecord(Exception, String.Format("{0}_{1}", ModuleName, FunctionName), ErrorCategory.NotSpecified, Target);
            }
            #endregion Exception Integration

            #region Error handling
            if (ErrorRecord != null)
            {
                if (!_fromStopFunction)
                    if (EnableException)
                        foreach (ErrorRecord record in ErrorRecord)
                            WriteError(record);

                LogHost.WriteErrorEntry(ErrorRecord, FunctionName, ModuleName, _Tags, _timestamp, _MessageSystem, System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InstanceId, Environment.MachineName);
            }
            #endregion Error handling

            LogEntryType channels = LogEntryType.None;

            #region Warning handling
            if (Level == MessageLevel.Warning)
            {
                if (!_silent)
                {
                    if (!String.IsNullOrEmpty(Once))
                    {
                        string onceName = String.Format("MessageOnce.{0}.{1}", FunctionName, Once).ToLower();
                        if (!(Configuration.ConfigurationHost.Configurations.ContainsKey(onceName) && (bool)Configuration.ConfigurationHost.Configurations[onceName].Value))
                        {
                            WriteWarning(_MessageStreams);
                            channels = channels | LogEntryType.Warning;

                            Configuration.Config cfg = new Configuration.Config();
                            cfg.Module = "messageonce";
                            cfg.Name = String.Format("{0}.{1}", FunctionName, Once).ToLower();
                            cfg.Hidden = true;
                            cfg.Description = "Locking setting that disables further display of the specified message";
                            cfg.Value = true;

                            Configuration.ConfigurationHost.Configurations[onceName] = cfg;
                        }
                    }
                    else
                    {
                        WriteWarning(_MessageStreams);
                        channels = channels | LogEntryType.Warning;
                    }
                }
                WriteDebug(_MessageStreams);
                channels = channels | LogEntryType.Debug;
            }
            #endregion Warning handling

            #region Message handling
            if (!_silent)
            {
                if ((MessageHost.MaximumInformation >= (int)Level) && (MessageHost.MinimumInformation <= (int)Level))
                {
                    if (!String.IsNullOrEmpty(Once))
                    {
                        string onceName = String.Format("MessageOnce.{0}.{1}", FunctionName, Once).ToLower();
                        if (!(Configuration.ConfigurationHost.Configurations.ContainsKey(onceName) && (bool)Configuration.ConfigurationHost.Configurations[onceName].Value))
                        {
                            InvokeCommand.InvokeScript(false, ScriptBlock.Create(_writeHostScript), null, _MessageHost);
                            channels = channels | LogEntryType.Information;

                            Configuration.Config cfg = new Configuration.Config();
                            cfg.Module = "messageonce";
                            cfg.Name = String.Format("{0}.{1}", FunctionName, Once).ToLower();
                            cfg.Hidden = true;
                            cfg.Description = "Locking setting that disables further display of the specified message";
                            cfg.Value = true;

                            Configuration.ConfigurationHost.Configurations[onceName] = cfg;
                        }
                    }
                    else
                    {
                        //InvokeCommand.InvokeScript(_writeHostScript, _MessageHost);
                        InvokeCommand.InvokeScript(false, ScriptBlock.Create(_writeHostScript), null, _MessageHost);
                        channels = channels | LogEntryType.Information;
                    }
                }
            }

            if ((MessageHost.MaximumVerbose >= (int)Level) && (MessageHost.MinimumVerbose <= (int)Level))
            {
                if ((_callStack.Count() > 1) && _callStack.ElementAt(_callStack.Count() - 2).InvocationInfo.BoundParameters.ContainsKey("Verbose"))
                    InvokeCommand.InvokeScript(@"$VerbosePreference = 'Continue'");
                //SessionState.PSVariable.Set("VerbosePreference", ActionPreference.Continue);

                WriteVerbose(_MessageStreams);
                channels = channels | LogEntryType.Verbose;
            }

            if ((MessageHost.MaximumDebug >= (int)Level) && (MessageHost.MinimumDebug <= (int)Level))
            {
                bool restoreInquire = false;
                if (_isDebug)
                {
                    if (Breakpoint.ToBool())
                        InvokeCommand.InvokeScript(false, ScriptBlock.Create(@"$DebugPreference = 'Inquire'"), null, null);
                    else
                    {
                        InvokeCommand.InvokeScript(false, ScriptBlock.Create(@"$DebugPreference = 'Continue'"), null, null);
                        restoreInquire = true;
                    }
                    WriteDebug(String.Format("{0} | {1}", Line, _MessageStreams));
                    channels = channels | LogEntryType.Debug;
                }
                else
                {
                    WriteDebug(_MessageStreams);
                    channels = channels | LogEntryType.Debug;
                }

                if (restoreInquire)
                    InvokeCommand.InvokeScript(false, ScriptBlock.Create(@"$DebugPreference = 'Inquire'"), null, null);
            }
            #endregion Message handling

            #region Logging
            LogEntry entry = LogHost.WriteLogEntry(_MessageSystem, channels, _timestamp, FunctionName, ModuleName, _Tags, Level, System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InstanceId, Environment.MachineName, File, Line, _callStack, String.Format("{0}\\{1}", Environment.UserDomainName, Environment.UserName), Target);
            #endregion Logging

            foreach (MessageEventSubscription subscription in MessageHost.Events.Values)
                if (subscription.Applies(entry))
                {
                    try { InvokeCommand.InvokeScript(subscription.ScriptBlock.ToString(), entry); }
                    catch (Exception e) { WriteError(new ErrorRecord(e, "", ErrorCategory.NotSpecified, entry)); }
                }
        }
        #endregion Cmdlet Implementation

        #region Helper methods
        /// <summary>
        /// Processes the target transform rules on an input object
        /// </summary>
        /// <param name="Item">The item to transform</param>
        /// <returns>The transformed object</returns>
        private object ResolveTarget(object Item)
        {
            if (Item == null)
                return null;

            string lowTypeName = Item.GetType().FullName.ToLower();

            if (MessageHost.TargetTransforms.ContainsKey(lowTypeName))
            {
                try { return InvokeCommand.InvokeScript(false, ScriptBlock.Create(MessageHost.TargetTransforms[lowTypeName].ToString()), null, Item); }
                catch (Exception e)
                {
                    MessageHost.WriteTransformError(new ErrorRecord(e, "Write-Message", ErrorCategory.OperationStopped, null), FunctionName, ModuleName, Item, TransformType.Target, System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InstanceId);
                    return Item;
                }
            }

            TransformCondition transform = MessageHost.TargetTransformlist.Get(lowTypeName, ModuleName, FunctionName);
            if (transform != null)
            {
                try { return InvokeCommand.InvokeScript(false, ScriptBlock.Create(transform.ScriptBlock.ToString()), null, Item); }
                catch (Exception e)
                {
                    MessageHost.WriteTransformError(new ErrorRecord(e, "Write-Message", ErrorCategory.OperationStopped, null), FunctionName, ModuleName, Item, TransformType.Target, System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InstanceId);
                    return Item;
                }
            }

            return Item;
        }

        /// <summary>
        /// Processes the specified exception specified
        /// </summary>
        /// <param name="Item">The exception to process</param>
        /// <returns>The transformed exception</returns>
        private Exception ResolveException(Exception Item)
        {
            if (Item == null)
                return Item;

            string lowTypeName = Item.GetType().FullName.ToLower();

            if (MessageHost.ExceptionTransforms.ContainsKey(lowTypeName))
            {
                try { return (Exception)InvokeCommand.InvokeScript(false, ScriptBlock.Create(MessageHost.ExceptionTransforms[lowTypeName].ToString()), null, Item)[0].BaseObject; }
                catch (Exception e)
                {
                    MessageHost.WriteTransformError(new ErrorRecord(e, "Write-Message", ErrorCategory.OperationStopped, null), FunctionName, ModuleName, Item, TransformType.Exception, System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InstanceId);
                    return Item;
                }
            }

            TransformCondition transform = MessageHost.ExceptionTransformList.Get(lowTypeName, ModuleName, FunctionName);
            if (transform != null)
            {
                try { return (Exception)InvokeCommand.InvokeScript(false, ScriptBlock.Create(transform.ScriptBlock.ToString()), null, Item)[0].BaseObject; }
                catch (Exception e)
                {
                    MessageHost.WriteTransformError(new ErrorRecord(e, "Write-Message", ErrorCategory.OperationStopped, null), FunctionName, ModuleName, Item, TransformType.Exception, System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InstanceId);
                    return Item;
                }
            }

            return Item;
        }

        /// <summary>
        /// Processs the input level and apply policy and rules
        /// </summary>
        /// <param name="Level">The original level of the message</param>
        /// <returns>The processed level</returns>
        private MessageLevel ResolveLevel(MessageLevel Level)
        {
            int tempLevel = (int)Level;

            if (MessageHost.NestedLevelDecrement > 0)
            {
                int depth = _stackDepth - 2;
                if (_fromStopFunction)
                    depth--;
                tempLevel = tempLevel + depth * MessageHost.NestedLevelDecrement;
            }

            if (MessageHost.MessageLevelModifiers.Count > 0)
                foreach (MessageLevelModifier modifier in MessageHost.MessageLevelModifiers.Values)
                    if (modifier.AppliesTo(FunctionName, ModuleName, _Tags))
                        tempLevel = tempLevel + modifier.Modifier;

            if (tempLevel > 9)
                tempLevel = 9;
            if (tempLevel < 1)
                tempLevel = 1;

            return (MessageLevel)tempLevel;
        }

        /// <summary>
        /// Builds the message item for display of Verbose, Warning and Debug streams
        /// </summary>
        /// <returns>The message to return</returns>
        private string GetMessage()
        {
            if (!String.IsNullOrEmpty(_message))
                return _message;
            if (MessageHost.EnableMessageTimestamp && MessageHost.EnableMessageBreadcrumbs)
                _message = String.Format("[{0}]{1}{2}", _timestamp.ToString("HH:mm:ss"), _BreadCrumbsString, GetMessageSimple());
            else if (MessageHost.EnableMessageTimestamp && MessageHost.EnableMessageDisplayCommand)
                _message = String.Format("[{0}][{1}] {2}", _timestamp.ToString("HH:mm:ss"), FunctionName, GetMessageSimple());
            else if (MessageHost.EnableMessageTimestamp)
                _message = String.Format("[{0}] {1}", _timestamp.ToString("HH:mm:ss"), GetMessageSimple());
            else if (MessageHost.EnableMessageBreadcrumbs)
                _message = String.Format("{0}{1}", _BreadCrumbsString, GetMessageSimple());
            else if (MessageHost.EnableMessageDisplayCommand)
                _message = String.Format("[{0}] {1}", FunctionName, GetMessageSimple());
            else
                _message = GetMessageSimple();

            return _message;
        }

        /// <summary>
        /// Builds the base message for internal system use.
        /// </summary>
        /// <returns>The message to return</returns>
        private string GetMessageSimple()
        {
            if (!String.IsNullOrEmpty(_messageSimple))
                return _messageSimple;

            string baseMessage = _errorQualifiedMessage;
            foreach (Match match in Regex.Matches(baseMessage, "<c=[\"'](.*?)[\"']>(.*?)</c>")) baseMessage = Regex.Replace(baseMessage, Regex.Escape(match.Value), match.Groups[2].Value); _messageSimple = baseMessage; return _messageSimple; } /// <summary> /// Builds the message item if needed and returns it /// </summary> /// <returns>The message to return</returns> private string GetMessageColor() { if (!String.IsNullOrEmpty(_messageColor)) return _messageColor; if (MessageHost.EnableMessageTimestamp && MessageHost.EnableMessageBreadcrumbs) _messageColor = String.Format("[<c='sub'>{0}</c>]{1} {2}", _timestamp.ToString("HH:mm:ss"), _BreadCrumbsStringColored, _errorQualifiedMessage); else if (MessageHost.EnableMessageTimestamp && MessageHost.EnableMessageDisplayCommand) _messageColor = String.Format("[<c='sub'>{0}</c>][<c='sub'>{1}</c>] {2}", _timestamp.ToString("HH:mm:ss"), FunctionName, _errorQualifiedMessage); else if (MessageHost.EnableMessageTimestamp) _messageColor = String.Format("[<c='sub'>{0}</c>] {1}", _timestamp.ToString("HH:mm:ss"), _errorQualifiedMessage); else if (MessageHost.EnableMessageBreadcrumbs) _messageColor = String.Format("{0}{1}", _BreadCrumbsStringColored, _errorQualifiedMessage);
            else if (MessageHost.EnableMessageDisplayCommand)
                _messageColor = String.Format("
[<c='sub'>{0}</c>] {1}", FunctionName, _errorQualifiedMessage);
            else
                _messageColor = _errorQualifiedMessage;
 
            return _messageColor;
        }
 
        /// <summary>
        /// Non-host output in developermode
        /// </summary>
        /// <returns>The string to write on messages that don't go straight to Write-HostColor</returns>
        private string GetMessageDeveloper()
        {
            if (!String.IsNullOrEmpty(_messageDeveloper))
                return _messageDeveloper;
 
            string targetString = "";
            if (Target != null)
            {
                if (Target.ToString() != Target.GetType().FullName)
                    targetString = String.Format("
 [T: {0}] ", Target.ToString());
                else
                    targetString = String.Format("
 [T: <{0}>] ", Target.GetType().Name);
            }
 
            List<string> channelList = new List<string>();
            if (!_silent)
            {
                if (Level == MessageLevel.Warning)
                    channelList.Add("
Warning"); if ((MessageHost.MaximumInformation >= (int)Level) && (MessageHost.MinimumInformation <= (int)Level)) channelList.Add("Information"); } if ((MessageHost.MaximumVerbose >= (int)Level) && (MessageHost.MinimumVerbose <= (int)Level)) channelList.Add("Verbose"); if ((MessageHost.MaximumDebug >= (int)Level) && (MessageHost.MinimumDebug <= (int)Level)) channelList.Add("Debug"); _messageDeveloper = String.Format(@"[{0}][{1}][L: {2}]{3}[C: {4}][EE: {5}][O: {6}]
    {7}", _timestamp.ToString("HH:mm:ss"), FunctionName, Level, targetString, String.Join(",", channelList), EnableException, (!String.IsNullOrEmpty(Once)), GetMessageSimple());
 
            return _messageDeveloper;
        }
 
        /// <summary>
        /// Host output in developermode
        /// </summary>
        /// <returns>The string to write on messages that go straight to Write-HostColor</returns>
        private string GetMessageDeveloperColor()
        {
            if (!String.IsNullOrEmpty(_messageDeveloperColor))
                return _messageDeveloperColor;
 
            string targetString = "";
            if (Target != null)
            {
                if (Target.ToString() != Target.GetType().FullName)
                    targetString = String.Format("
 [<c='sub'>T:</c> <c='em'>{0}</c>] ", Target.ToString());
                else
                    targetString = String.Format("
 [<c='sub'>T:</c> <c='em'><{0}></c>] ", Target.GetType().Name);
            }
 
            List<string> channelList = new List<string>();
            if (!_silent)
            {
                if (Level == MessageLevel.Warning)
                    channelList.Add("
Warning"); if ((MessageHost.MaximumInformation >= (int)Level) && (MessageHost.MinimumInformation <= (int)Level)) channelList.Add("Information"); } if ((MessageHost.MaximumVerbose >= (int)Level) && (MessageHost.MinimumVerbose <= (int)Level)) channelList.Add("Verbose"); if ((MessageHost.MaximumDebug >= (int)Level) && (MessageHost.MinimumDebug <= (int)Level)) channelList.Add("Debug"); _messageDeveloperColor = String.Format(@"[<c='sub'>{0}</c>][<c='sub'>{1}</c>][<c='sub'>L:</c> <c='em'>{2}</c>]{3}[<c='sub'>C: <c='em'>{4}</c>][<c='sub'>EE: <c='em'>{5}</c>][<c='sub'>O: <c='em'>{6}</c>]
    {7}", _timestamp.ToString("HH:mm:ss"), FunctionName, Level, targetString, String.Join(",", channelList), EnableException, (!String.IsNullOrEmpty(Once)), _errorQualifiedMessage);
 
            return _messageDeveloperColor;
        }
        #endregion Helper methods
    }
}