aktueller stand

This commit is contained in:
Meik
2026-02-03 16:47:06 +01:00
parent 82984f769b
commit d289fc4c21
21 changed files with 1603 additions and 1141 deletions

View File

@@ -520,8 +520,12 @@ namespace C4IT.DataHistoryProvider
if(HasIntuneConfig)
{
Connectors.Add(enumDataHistoryOrigin.Intune, ActiveDirectory);
RegisterSearchRelationProvider(ActiveDirectory);
}
//if (HasIntuneConfig && HasMobileDeviceConfig)
//{
// RegisterSearchRelationProvider(ActiveDirectory);
//}
// do the post configuration tasks afer the infrastructure was loaded
DoProcessUiMessage(1, "processing post configuration tasks");
@@ -6491,6 +6495,34 @@ namespace C4IT.DataHistoryProvider
return null;
}
public cCollectorStatusInfo GetCollectorStatus(cF4sdWebRequestInfo requestInfo)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try
{
var _cfgInfrastructure = new cDataHistoryConfigInfrastructure();
_cfgInfrastructure.LoadFromFile(out var PMs);
var retVal = new cCollectorStatusInfo();
retVal.HasM42Config = _cfgInfrastructure.M42Wpm != null;
retVal.HasIntuneConfig = (_cfgInfrastructure.AzureTenants?.Values.Any(t => t.ScanIntuneDevices) == true);
retVal.HasMobileDeviceConfig = (_cfgInfrastructure.AzureTenants?.Values.Any(t => t.WithMobileDevices) == true);
retVal.HasCitrixConfig = (InfrastructureConfig.Citrix != null);
return retVal;
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (CM != null) LogMethodEnd(CM);
}
return null;
}
public override async Task<cF4SdUserInfoChange> ValidateTokenAsync(cF4SDTokenRegistration TokenRegistration, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
@@ -6723,6 +6755,14 @@ namespace C4IT.DataHistoryProvider
}
}
public class cCollectorStatusInfo
{
public bool HasM42Config;
public bool HasIntuneConfig;
public bool HasMobileDeviceConfig;
public bool HasCitrixConfig;
}
public class cDataHistorySHistoryScanInfo
{
public Guid ID;

View File

@@ -1545,10 +1545,10 @@ namespace C4IT.DataHistoryProvider
var strUrl = string.Format(constMsGraphUserList, constMsGraphUserPaging);
if (!string.IsNullOrEmpty(Tenant.ScanFilter))
strUrl += " and (" + Tenant.ScanFilter + ")";
if (!string.IsNullOrEmpty(Tenant.ScanFilterUser))
strUrl += " and (" + Tenant.ScanFilterUser + ")";
var Result = await MsGraph.RequestListAsync(strUrl, UseBeta: false, retryForbidden: false, loadPaged: true, ConsistencyLevelEventual: Tenant.UseConsistencyLevelEventual);
var Result = await MsGraph.RequestListAsync(strUrl, UseBeta: Tenant.UseBetaUserFilter, retryForbidden: false, loadPaged: true, ConsistencyLevelEventual: Tenant.ConsistencyLevelEventualUserFilter);
var Count = 0;
var Updated = 0;
@@ -2058,7 +2058,7 @@ namespace C4IT.DataHistoryProvider
try
{
List<cF4sdConnectorIds> Ids = await _collector.getConnectorIdList(ids.ToList(), token, null, 0);
List<cF4sdApiSearchResultRelation> relations = await GetMobileDeviceAsync(Ids, token, null);
List<cF4sdApiSearchResultRelation> relations = await GetUserMobileDevicesAsync(Ids, token, null);
return new cF4sdStagedSearchResultRelations() { Relations = relations };
}
catch (Exception ex)
@@ -2505,7 +2505,7 @@ namespace C4IT.DataHistoryProvider
return null;
}
public async Task<List<cF4sdApiSearchResultRelation>> GetMobileDeviceAsync(List<cF4sdConnectorIds> UserId, CancellationToken Token, cF4sdWebRequestInfo requestInfo)
public async Task<List<cF4sdApiSearchResultRelation>> GetUserMobileDevicesAsync(List<cF4sdConnectorIds> UserId, CancellationToken Token, cF4sdWebRequestInfo requestInfo)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
try
@@ -2513,34 +2513,23 @@ namespace C4IT.DataHistoryProvider
string userIds = string.Join(",", UserId.Where(u => !string.IsNullOrWhiteSpace(u?.intuneId.ToString())).Select(u => u.intuneId));
var tenantId = UserId?.FirstOrDefault(u => u?.tenantId != Guid.Empty)?.tenantId ?? Guid.Empty;
var userId_str = "ef333f80-0ae1-40b3-826f-2dc69c80c1f0";
var intuneId = new Guid(userId_str);
var strUrl = string.Format(constMsGraphManagedMobileDeviceList, intuneId);
var strUrl = string.Format(constMsGraphManagedMobileDeviceList, userIds);
var MsGraph = await GetGraphForTenantAsync(tenantId, true);
if (MsGraph == null)
return null;
// Schritt 1: Abrufen der Recovery-Key IDs
var Result = await MsGraph.RequestListAsync(strUrl, UseBeta: false, retryForbidden: false);
if (Result != null == Result.Count > 0)
{
var searchResult = new List<cF4sdApiSearchResultRelation>(Result.Count);
//double _sumDuration = 0;
foreach (var Entry in Result)
{
var endDate = DateTime.MinValue;
if (!DateTime.TryParse(Entry.Result.lastSyncDateTime?.ToString(), out endDate))
endDate = DateTime.UtcNow;
//var startDate = DateTime.MinValue;
//if (!DateTime.TryParse(Entry.Result.StartDate?.ToString(), out startDate))
// startDate = DateTime.UtcNow;
//TimeSpan _duration = endDate - startDate;
//_sumDuration += _duration.TotalSeconds;
var searchInfo = new cF4sdApiSearchResultRelation()
{

View File

@@ -45,6 +45,8 @@ namespace C4IT.DataHistoryProvider
public const string constCitrixSessionMachine = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=Machine($expand=CurrentLoadIndex,Catalog,DesktopGroup($select=Name)),Machine";
public const string constCitrixSessionCurrentConnection = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=CurrentConnection,LogOnMetrics";
public const string constCitrixSessionConnections = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=Connection";
public const string constCitrixSessionLogInSummaries = "monitorodata/Sessions?$filter=UserId in ({0})$select=Id,StartDate&$expand=User($select=UserName),LogOnSummaries($select=LogOnDuration,AuthenticationDuration,BrokingDuration,ProfileLoadDuration,GpoProcessingDuration,InteractiveSessionDuration)";
public const string constCitrixSessionLogInSummaries2 = "monitorodata/LogOnSummaries?$expand=Session($expand=User)&$filter=UserId eq {0}";
public const string constCitrixUserConnections = "monitorodata/Sessions?$filter=UserId in ({0})&$expand=Connection";
public const string constCitrixSessionUser = "monitorodata/Sessions?$select=SessionKey&$filter=SessionKey eq {0}&$expand=User";
public const string constCitrixSessionLogoff = "cvad/manage/Sessions/{0}/$logoff";
@@ -55,6 +57,7 @@ namespace C4IT.DataHistoryProvider
public const string constTableNameSessionConnection = "citrix-session-connection";
public const string constTableNameSessionCurrentConnection = "citrix-session-currentConnection";
public const string constTableNameSessionMetrics = "citrix-session-metrics";
public const string constTableNameSessionStateMetrics = "citrix-connectionState-metrics";
public const string constTableNameSessionUser = "citrix-session-user";
public const string constTableNameSessionDetailsIcaRttMS = "citrix-session-details-icaRttMS";
public const string constTableNameSessionDetailsIcaLatency = "citrix-session-details-icaLatency";
@@ -327,7 +330,7 @@ namespace C4IT.DataHistoryProvider
return null;
var res = await citrixCommunication.RequestListAsync(strUrl, retryForbidden: false);
if (res != null)
if (res != null == res.Count > 0)
{
JObject sessionInfos = res[0].Result;
var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
@@ -339,7 +342,13 @@ namespace C4IT.DataHistoryProvider
TableType = eDataHistoryTableType.Static
};
var dicVals = new List<Dictionary<string, object>>();
foreach (var Entry in res) {
}
var dicVals = new List<Dictionary<string, object>>();
var vals = sessionInfos.Children();
var metricsObj = sessionInfos["SessionMetrics"] as JArray;
@@ -408,8 +417,6 @@ namespace C4IT.DataHistoryProvider
var aggregatedValue = GetAggregationValue(valuesOfColumn, configColumn.AggregationType, colInfo.ValueType);
AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(aggregatedValue, colInfo.ValueType));
}
}
catch (Exception E)
{
@@ -446,6 +453,179 @@ namespace C4IT.DataHistoryProvider
}
}
private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionStateMetricsAsync(cF4sdConnectorIds UserIds, string TenantId, cF4sdWebRequestInfo RequestInfo, int MaxAge, int LogDeep, CancellationToken Token)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
if (cPerformanceLogger.IsActive && RequestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, RequestInfo.id, RequestInfo.created); }
var _startTime = DateTime.UtcNow;
try
{
var strUrl = string.Format(constCitrixSessions, UserIds.citrixUserId.ToString());
if (!Collector.ClusterConfig.Tables.TryGetValue(constTableNameSessionStateMetrics, out var SessionTableDetails))
return null;
var citrixCommunication = await GetCitrixCommunicationForTenantAsync(TenantId, true);
if (citrixCommunication == null)
return null;
var res = await citrixCommunication.RequestAsync(strUrl, retryForbidden: false);
if (res != null)
{
JObject sessionInfos = res.Result;
var retVal = new cF4SDHealthCardRawData.cHealthCardTable()
{
Name = constTableNameSessionStateMetrics,
InformationClass = enumFasdInformationClass.VirtualSession,
IsStatic = true,
TableType = eDataHistoryTableType.Static
};
var dicVals = new List<Dictionary<string, object>>();
var vals = sessionInfos.Children();
var metricsObj = sessionInfos["value"] as JArray;
if (metricsObj != null)
{
dicVals = metricsObj
.OfType<JObject>()
.Where(o =>
o["StartDate"] != null &&
o["ConnectionState"] != null &&
DateTime.TryParse(o["StartDate"].ToString(), out _)
)
.GroupBy(o => new
{
Date = DateTime.Parse(o["StartDate"].ToString()).Date,
ConnectionState = (int)o["ConnectionState"]
})
.OrderBy(g => g.Key.Date)
.Select(g => new Dictionary<string, object>
{
{ "StartDate", g.Key.Date }, // ✅ DateTime
{ "ConnectionState", g.Key.ConnectionState },
{ "ConnectionCount", g.Count() }
})
.ToList();
const string startDateColumnName = "StartDate";
const string connectionStatusColumnName = "ConnectionState";
var groupedValues = dicVals
.Where(v =>
v.TryGetValue(startDateColumnName, out var collectDate) &&
collectDate is DateTime &&
v.TryGetValue(connectionStatusColumnName, out _))
.GroupBy(v => new
{
StartDate = ((DateTime)v[startDateColumnName]).Date,
ConnectionStatus = v[connectionStatusColumnName]
})
.Select(g => new Dictionary<string, object>
{
{ startDateColumnName, g.Key.StartDate },
{ connectionStatusColumnName, g.Key.ConnectionStatus },
{ "ConnectionCount", g.Count() }
})
.ToLookup(d => new
{
StartDate = (DateTime)d[startDateColumnName],
ConnectionStatus = d[connectionStatusColumnName],
ConnectionCount = d["ConnectionCount"]
});
for (int i = 0; i < MaxAge; i++)
{
DateTime referenceDate = DateTime.Now.AddDays(-i).Date;
//var valuesOfDay = groupedValues.FirstOrDefault(v => v.Key.Date == referenceDate);
var valuesOfDay = groupedValues.Where(g => g.Key.StartDate == referenceDate).SelectMany(g => g).ToList();
foreach (var colInfo in SessionTableDetails.Columns.Values)
{
try
{
if (valuesOfDay is null)
{
AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(null, colInfo.ValueType));
continue;
}
var valuesOfColumn = valuesOfDay
.Select(v =>
{
if (v.TryGetValue(colInfo.SourceName, out var colValue))
return colValue;
else
return null;
})
.Where(v => v != null);
if (!valuesOfColumn.Any())
{
AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(null, colInfo.ValueType));
continue;
}
if (colInfo is cDataHistoryConfigColumn configColumn && configColumn.AggregationType != eDataHistoryAggregationType.Unknown)
{
var aggregatedValue = GetAggregationValue(valuesOfColumn, configColumn.AggregationType, colInfo.ValueType);
AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(aggregatedValue, colInfo.ValueType));
}
else
{
var value = valuesOfColumn.FirstOrDefault();
AddColumnData(retVal, colInfo.SourceName, ConvertToF4sdType(value, colInfo.ValueType));
}
}
catch (Exception E)
{
LogException(E);
}
}
}
return new List<cF4SDHealthCardRawData.cHealthCardTable>(1) { retVal };
}
else
{
LogEntry($"Could not get detailed sessionmetrics information from citrix with id {UserIds}. Sessionmetrics content is empty.", LogLevels.Warning);
}
}
}
catch (Exception E)
{
LogException(E);
}
finally
{
if (cPerformanceLogger.IsActive && RequestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, RequestInfo.id, RequestInfo.created, _startTime); }
if (CM != null) LogMethodEnd(CM);
}
return null;
void AddColumnData(cF4SDHealthCardRawData.cHealthCardTable table, string columnName, object value)
{
if (table.Columns.TryGetValue(columnName, out var columnValues))
columnValues.Values.Add(value);
else
table.Columns.Add(columnName, new cF4SDHealthCardRawData.cHealthCardTableColumn(table) { ColumnName = columnName, Values = new List<object>() { value } });
}
}
private async Task<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>> GetSessionMetricsDetailsIcaRttMSAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo,int MaxAge, DateTime RefTime, int LogDeep, CancellationToken Token)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
@@ -1658,7 +1838,6 @@ namespace C4IT.DataHistoryProvider
return null;
}
private async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetSessionAsync(Guid Sessionid, string TenantId, cF4sdWebRequestInfo RequestInfo, int LogDeep, CancellationToken Token)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
@@ -2205,7 +2384,7 @@ namespace C4IT.DataHistoryProvider
if (bDcLatencyDetailExists)
listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardDetailsTable>>(async () => { return await GetSessionMetricsDetailsDcLatencyAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, RefTime, LogDeep + 1, Token); }));
var arrRes = await Task.WhenAll(listTasks.ToArray());
var retVal = new List<cF4SDHealthCardRawData.cHealthCardDetailsTable>();
@@ -2401,8 +2580,8 @@ namespace C4IT.DataHistoryProvider
var _machineExists = Has(constTableNameSessionMachine);
var _connectionExists = Has(constTableNameSessionConnection);
var _currentConnectionExists = Has(constTableNameSessionCurrentConnection);
var _sessionMetricsExists = Has(constTableNameSessionMetrics);
var _sessionMetricsExists = Has(constTableNameSessionMetrics);
var retVal = new List<cF4SDHealthCardRawData.cHealthCardTable>();
var listTasks = new List<Task<List<cF4SDHealthCardRawData.cHealthCardTable>>>(2);
@@ -2420,7 +2599,7 @@ namespace C4IT.DataHistoryProvider
//else
listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionAsync(session.Id, userIds.citrixTenantId, requestInfo, LogDeep + 1, Token); }));
if (_sessionMetricsExists)
if (_sessionMetricsExists) {
//if (CacheId == null && !instantly)
//{
// retVal.Add(new cF4SDHealthCardRawData.cHealthCardTable()
@@ -2433,8 +2612,9 @@ namespace C4IT.DataHistoryProvider
//}
//else
listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionMetricsAsync(session.Id, userIds.citrixTenantId, requestInfo, MaxAge, LogDeep + 1, Token); }));
listTasks.Add(Task.Run<List<cF4SDHealthCardRawData.cHealthCardTable>>(async () => { return await GetSessionStateMetricsAsync(userIds, userIds.citrixTenantId, requestInfo, MaxAge, LogDeep + 1, Token); }));
}
if (_machineExists)
//if (CacheId == null && !instantly)

View File

@@ -709,7 +709,10 @@ namespace C4IT.DataHistoryProvider
{
var relations = JsonConvert.DeserializeObject<List<cF4sdApiSearchResultRelation>>(json);
if (relations != null)
{
await EnsureTicketOverviewRelationIdentitiesAsync(relations, requestInfo, LogDeep + 1, Token);
return relations;
}
}
}
@@ -727,6 +730,94 @@ namespace C4IT.DataHistoryProvider
return new List<cF4sdApiSearchResultRelation>();
}
private async Task EnsureTicketOverviewRelationIdentitiesAsync(
List<cF4sdApiSearchResultRelation> relations,
cF4sdWebRequestInfo requestInfo,
int logDeep,
CancellationToken token)
{
if (relations == null)
return;
foreach (var relation in relations)
{
if (token.IsCancellationRequested || relation == null)
return;
var identities = relation.Identities ?? new cF4sdIdentityList();
relation.Identities = identities;
if (!identities.Any(i => i.Class == enumFasdInformationClass.Ticket) && relation.id != Guid.Empty)
{
identities.Add(new cF4sdIdentityEntry
{
Class = enumFasdInformationClass.Ticket,
Id = relation.id
});
}
if (relation.Infos == null)
relation.Infos = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (!relation.Infos.ContainsKey("Sids") && relation.Infos.TryGetValue("UserSid", out var userSid) && !string.IsNullOrWhiteSpace(userSid))
relation.Infos["Sids"] = userSid;
Guid userId = Guid.Empty;
if (relation.Infos.Count > 0)
{
if (relation.Infos.TryGetValue("Sids", out var sidsValue))
{
var sids = sidsValue?.Split(',')
.Select(v => v.Trim())
.Where(v => !string.IsNullOrWhiteSpace(v))
.ToList();
if (sids != null && sids.Count > 0)
{
var resolved = await Collector.UserSearchBySidsAsync(sids, token, requestInfo, logDeep + 1);
var user = resolved?.Values?.FirstOrDefault()?.FirstOrDefault();
if (user?.Result != null)
userId = user.Result.id;
}
}
if (userId == Guid.Empty && relation.Infos.TryGetValue("UserAccount", out var userAccount))
{
relation.Infos.TryGetValue("UserDomain", out var userDomain);
if (!string.IsNullOrWhiteSpace(userAccount))
{
var resolved = await Collector.GetUserIdFromAccountAsync(userAccount, userDomain, requestInfo, logDeep + 1, token);
if (resolved.HasValue)
userId = resolved.Value;
}
}
if (userId == Guid.Empty)
{
if (relation.Infos.TryGetValue("UserId", out var userIdString) ||
relation.Infos.TryGetValue("UserGuid", out userIdString) ||
relation.Infos.TryGetValue("UserIdentityId", out userIdString))
{
Guid.TryParse(userIdString, out userId);
}
}
}
if (userId == Guid.Empty)
continue;
identities.RemoveAll(i => i.Class == enumFasdInformationClass.User);
identities.Add(new cF4sdIdentityEntry
{
Class = enumFasdInformationClass.User,
Id = userId
});
relation.Infos["UserId"] = userId.ToString();
relation.Infos["UserGuid"] = userId.ToString();
}
}
public override async Task<List<cF4SDHealthCardRawData.cHealthCardTable>> GetTableResultsVirtualAsync(List<cDataHistoryConfigTable> Tables, Dictionary<enumFasdInformationClass, cF4sdConnectorIds> Identities, DateTime RefTime, int MaxAge, bool instantly, Guid? CacheId, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep)
{
@@ -2669,7 +2760,7 @@ namespace C4IT.DataHistoryProvider
if (response.IsSuccessStatusCode)
{
//await Collector.ReportTicketAsync(ticketInfo, requestInfo, LogDeep + 1, token);
await Collector.ReportTicketAsync(ticketInfo, requestInfo, LogDeep + 1, token);
return string.Empty;
}
}
@@ -2918,7 +3009,7 @@ namespace C4IT.DataHistoryProvider
if (response.IsSuccessStatusCode)
{
//await Collector.ReportTicketAsync(ticketInfo, requestInfo, LogDeep + 1, token);
await Collector.ReportTicketAsync(ticketInfo, requestInfo, LogDeep + 1, token);
return string.Empty;
}
}

View File

@@ -511,8 +511,10 @@ namespace C4IT.DataHistoryProvider
public bool ScanIntuneDevices { get; private set; } = false;
public bool WithMobileDevices { get; private set; } = false;
public string ScanFilter { get; private set; } = "";
public bool UseConsistencyLevelEventual { get; private set; } = false;
public string ScanFilterUser { get; private set; } = "";
public bool ConsistencyLevelEventualUserFilter { get; private set; } = false;
public bool UseBetaUserFilter { get; private set; } = false;
internal cDataHistoryAzureTenant(XmlElement XNode, Dictionary<string, cCredential> Credentials, cXmlParser Parser)
{
@@ -547,18 +549,19 @@ namespace C4IT.DataHistoryProvider
ScanIntuneDevices = cXmlParser.GetBoolFromXmlAttribute(XNode, "ScanIntuneDevices");
WithMobileDevices = cXmlParser.GetBoolFromXmlAttribute(XNode, "WithMobileDevices");
var XFilterNode = XNode.SelectSingleNode("Azure-Scan-Filter");
var XFilterNode = XNode.SelectSingleNode("Azure-Filter-User");
if (XFilterNode is XmlElement XFilter)
{
Parser.EnterElement("Azure-AD");
Parser.EnterElement("Azure-Filter-User");
try
{
ScanFilter = XFilter.SelectSingleNode("text()")?.Value;
UseConsistencyLevelEventual = cXmlParser.GetBoolFromXmlAttribute(XFilter, "UseConsistencyLevelEventual");
ScanFilterUser = XFilter.SelectSingleNode("text()")?.Value;
ConsistencyLevelEventualUserFilter = cXmlParser.GetBoolFromXmlAttribute(XFilter, "UseConsistencyLevelEventual");
UseBetaUserFilter = cXmlParser.GetBoolFromXmlAttribute(XFilter, "UseBetaEnpoint");
}
finally
{
Parser.LeaveElement("Azure-AD");
Parser.LeaveElement("Azure-Filter-User");
}
}