Skip to content

Commit 3689db3

Browse files
AMSoftwareNLArjan Meskers
andauthored
MSALIdentityLogger and ServiceClientLogger (#9)
Co-authored-by: Arjan Meskers <arjanmeskers@outlook.com>
1 parent b236255 commit 3689db3

6 files changed

Lines changed: 329 additions & 20 deletions

File tree

src/AMSoftware.Dataverse.PowerShell/CmdletBase.cs

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1515
You should have received a copy of the GNU General Public License
1616
along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
*/
18+
using Microsoft.Extensions.Logging;
1819
using Microsoft.Xrm.Sdk;
1920
using System;
21+
using System.Collections.Concurrent;
2022
using System.Management.Automation;
2123
using System.ServiceModel;
2224

2325
namespace AMSoftware.Dataverse.PowerShell
2426
{
2527
public abstract class CmdletBase : PSCmdlet
2628
{
29+
private ConcurrentQueue<PSLogEntry> LogMessages { get; set; } = new ConcurrentQueue<PSLogEntry>();
30+
2731
public virtual void Execute()
2832
{
2933
// Do nothing
@@ -39,6 +43,16 @@ protected override void BeginProcessing()
3943
{
4044
WriteVerboseWithTimestamp(string.Format("{0} begin processing with ParameterSet '{1}'.", GetType().Name, ParameterSetName));
4145
}
46+
47+
MSALIdentityLogger.Instance.AddContext((entry) =>
48+
{
49+
LogMessages.Enqueue(entry);
50+
});
51+
ServiceClientLogger.Instance.AddContext((entry) =>
52+
{
53+
LogMessages.Enqueue(entry);
54+
});
55+
4256
base.BeginProcessing();
4357
}
4458

@@ -69,23 +83,101 @@ protected override void ProcessRecord()
6983

7084
protected override void EndProcessing()
7185
{
86+
MSALIdentityLogger.Instance.RemoveContext();
87+
ServiceClientLogger.Instance.RemoveContext();
88+
89+
FlushLogMessages();
90+
7291
WriteVerboseWithTimestamp(string.Format("{0} end processing.", GetType().Name));
7392

7493
base.EndProcessing();
7594
}
7695

77-
protected new void WriteObject(object sendToPipeline)
96+
private void FlushLogMessages()
7897
{
79-
//FlushDebugMessages();
80-
base.WriteObject(sendToPipeline);
98+
while (LogMessages.TryDequeue(out PSLogEntry entry))
99+
{
100+
switch (entry.LogLevel)
101+
{
102+
case LogLevel.Warning:
103+
base.WriteWarning(entry.Message);
104+
break;
105+
case LogLevel.Debug:
106+
base.WriteDebug(entry.Message);
107+
break;
108+
case LogLevel.Trace:
109+
base.WriteVerbose(entry.Message);
110+
break;
111+
}
112+
}
113+
}
114+
115+
protected new void ThrowTerminatingError(ErrorRecord errorRecord)
116+
{
117+
FlushLogMessages();
118+
base.ThrowTerminatingError(errorRecord);
119+
}
120+
121+
protected new void WriteCommandDetail(string text)
122+
{
123+
FlushLogMessages();
124+
base.WriteCommandDetail(text);
125+
}
126+
127+
protected new void WriteDebug(string text)
128+
{
129+
FlushLogMessages();
130+
base.WriteDebug(text);
131+
}
132+
133+
protected new void WriteError(ErrorRecord errorRecord)
134+
{
135+
FlushLogMessages();
136+
base.WriteError(errorRecord);
137+
}
138+
139+
protected new void WriteInformation(object messageData, string[] tags)
140+
{
141+
FlushLogMessages();
142+
base.WriteInformation(messageData, tags);
143+
}
144+
145+
protected new void WriteInformation(InformationRecord informationRecord)
146+
{
147+
FlushLogMessages();
148+
base.WriteInformation(informationRecord);
81149
}
82150

83151
protected new void WriteObject(object sendToPipeline, bool enumerateCollection)
84152
{
85-
//FlushDebugMessages();
153+
FlushLogMessages();
86154
base.WriteObject(sendToPipeline, enumerateCollection);
87155
}
88156

157+
protected new void WriteObject(object sendToPipeline)
158+
{
159+
FlushLogMessages();
160+
base.WriteObject(sendToPipeline);
161+
}
162+
163+
protected new void WriteProgress(ProgressRecord progressRecord)
164+
{
165+
FlushLogMessages();
166+
base.WriteProgress(progressRecord);
167+
}
168+
169+
protected new void WriteVerbose(string text)
170+
{
171+
FlushLogMessages();
172+
base.WriteVerbose(text);
173+
}
174+
175+
protected new void WriteWarning(string text)
176+
{
177+
FlushLogMessages();
178+
base.WriteWarning(text);
179+
}
180+
89181
protected bool IsTerminatingError(Exception ex)
90182
{
91183
var pipelineStoppedEx = ex as PipelineStoppedException;

src/AMSoftware.Dataverse.PowerShell/Commands/ConnectEnvironmentCommand.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ You should have received a copy of the GNU General Public License
1616
along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
*/
1818
using Microsoft.PowerPlatform.Dataverse.Client;
19-
using Microsoft.PowerPlatform.Dataverse.Client.Auth;
2019
using Microsoft.PowerPlatform.Dataverse.Client.Model;
2120
using System;
2221
using System.Management.Automation;
@@ -67,27 +66,29 @@ protected override void BeginProcessing()
6766
switch (ParameterSetName)
6867
{
6968
case ConnectionstringParameterSet:
70-
client = new ServiceClient(Connectionstring);
69+
client = new ServiceClient(Connectionstring, ServiceClientLogger.Instance);
7170
break;
7271
case ClientSecretParameterSet:
73-
client = new ServiceClient(EnvironmentUrl, ClientId, ClientSecret, true);
72+
client = new ServiceClient(EnvironmentUrl, ClientId, ClientSecret, true, ServiceClientLogger.Instance);
7473
break;
7574
case InteractiveParameterSet:
7675
if (UseDeviceCode.ToBool())
7776
{
78-
InteractiveAuthenticator authenticator = new InteractiveAuthenticator(ClientId);
77+
var authenticator = new InteractiveAuthenticator(ClientId);
7978
client = new ServiceClient(EnvironmentUrl, authenticator.AcquireEnvironmentTokenAsync, true);
8079
}
8180
else
8281
{
83-
client = new ServiceClient(new ConnectionOptions()
84-
{
85-
AuthenticationType = AuthenticationType.OAuth,
86-
RequireNewInstance = true,
87-
RedirectUri = new Uri("http://localhost"),
88-
ClientId = ClientId,
89-
ServiceUri = EnvironmentUrl
90-
}, false);
82+
client = new ServiceClient(
83+
new ConnectionOptions()
84+
{
85+
AuthenticationType = AuthenticationType.OAuth,
86+
RequireNewInstance = true,
87+
RedirectUri = new Uri("http://localhost"),
88+
ClientId = ClientId,
89+
ServiceUri = EnvironmentUrl,
90+
Logger = ServiceClientLogger.Instance
91+
}, false);
9192
}
9293
break;
9394
case ServiceClientParameterSet:

src/AMSoftware.Dataverse.PowerShell/InteractiveAuthenticator.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@ You should have received a copy of the GNU General Public License
1717
*/
1818
using Microsoft.Identity.Client;
1919
using System;
20-
using System.Collections.Generic;
2120
using System.Linq;
22-
using System.Text;
2321
using System.Threading.Tasks;
24-
using static System.Formats.Asn1.AsnWriter;
2522

2623
namespace AMSoftware.Dataverse.PowerShell
2724
{
@@ -38,12 +35,14 @@ internal InteractiveAuthenticator(string publicClientId)
3835
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
3936
.WithDefaultRedirectUri()
4037
.WithClientName("AMSoftware Dataverse PowerShell")
38+
.WithClientVersion("0.6.0")
39+
.WithLogging(MSALIdentityLogger.Instance.Log, LogLevel.Always, false, false)
4140
.Build();
4241
}
4342

4443
public Task<string> AcquireEnvironmentTokenAsync(string environmentUrl)
4544
{
46-
string[] authenticationScopes = InteractiveAuthenticator.BuildScopeFromUrl(environmentUrl);
45+
string[] authenticationScopes = BuildScopeFromUrl(environmentUrl);
4746
var authresult = AcquireEnvironmentTokenAsync(authenticationScopes).GetAwaiter().GetResult();
4847

4948
return Task.FromResult(authresult.AccessToken);
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
PowerShell Module for Power Platform Dataverse
3+
Copyright(C) 2024 AMSoftwareNL
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
using Microsoft.Identity.Client;
20+
using Microsoft.IdentityModel.Abstractions;
21+
using System;
22+
23+
namespace AMSoftware.Dataverse.PowerShell
24+
{
25+
internal sealed class MSALIdentityLogger : PSLoggerBase, IIdentityLogger
26+
{
27+
private static readonly MSALIdentityLogger _instance = new();
28+
29+
internal static MSALIdentityLogger Instance
30+
{
31+
get
32+
{
33+
return _instance;
34+
}
35+
}
36+
37+
private MSALIdentityLogger()
38+
{
39+
40+
}
41+
42+
public bool IsEnabled(EventLogLevel eventLogLevel)
43+
{
44+
return true;
45+
}
46+
47+
public void Log(LogLevel level, string message, bool containsPii)
48+
{
49+
if (_logWriter != null)
50+
{
51+
switch (level)
52+
{
53+
case LogLevel.Verbose:
54+
_logWriter(new PSLogEntry()
55+
{
56+
LogLevel = Microsoft.Extensions.Logging.LogLevel.Trace,
57+
Message = message
58+
});
59+
break;
60+
case LogLevel.Warning:
61+
_logWriter(new PSLogEntry()
62+
{
63+
LogLevel = Microsoft.Extensions.Logging.LogLevel.Warning,
64+
Message = message
65+
});
66+
break;
67+
case LogLevel.Always:
68+
_logWriter(new PSLogEntry()
69+
{
70+
LogLevel = Microsoft.Extensions.Logging.LogLevel.Debug,
71+
Message = message
72+
});
73+
break;
74+
}
75+
}
76+
}
77+
78+
public void Log(LogEntry entry)
79+
{
80+
if (_logWriter != null)
81+
{
82+
switch (entry.EventLogLevel)
83+
{
84+
case EventLogLevel.Verbose:
85+
_logWriter(new PSLogEntry()
86+
{
87+
LogLevel = Microsoft.Extensions.Logging.LogLevel.Trace,
88+
Message = entry.Message
89+
});
90+
break;
91+
case EventLogLevel.Warning:
92+
_logWriter(new PSLogEntry()
93+
{
94+
LogLevel = Microsoft.Extensions.Logging.LogLevel.Warning,
95+
Message = entry.Message
96+
});
97+
break;
98+
case EventLogLevel.LogAlways:
99+
_logWriter(new PSLogEntry()
100+
{
101+
LogLevel = Microsoft.Extensions.Logging.LogLevel.Debug,
102+
Message = entry.Message
103+
});
104+
break;
105+
}
106+
}
107+
}
108+
}
109+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
PowerShell Module for Power Platform Dataverse
3+
Copyright(C) 2024 AMSoftwareNL
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
using System;
20+
using Microsoft.Extensions.Logging;
21+
22+
namespace AMSoftware.Dataverse.PowerShell
23+
{
24+
internal abstract class PSLoggerBase
25+
{
26+
protected Action<PSLogEntry> _logWriter;
27+
28+
internal void AddContext(Action<PSLogEntry> logWriter)
29+
{
30+
_logWriter = logWriter;
31+
}
32+
33+
internal void RemoveContext()
34+
{
35+
_logWriter = null;
36+
}
37+
}
38+
39+
internal struct PSLogEntry
40+
{
41+
public LogLevel LogLevel { get; set; }
42+
public string Message { get; set; }
43+
}
44+
}

0 commit comments

Comments
 (0)