using System; using System.Collections.Generic; using System.ComponentModel.Design; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; using C4IT.HTTP; using C4IT.Logging; using Newtonsoft.Json.Linq; using static C4IT.Logging.cLogManager; namespace C4IT.FASD.Communication.Citrix { public class cCitrixApiResultList : List { public string NextResultUrl { get; private set; } = null; public bool retryForbidden { get; private set; } = false; public cCitrixApiResultList(bool retryForbidden) { this.retryForbidden = retryForbidden; } public bool hasRemaining { get { return !string.IsNullOrEmpty(NextResultUrl); } } public void AddResult(dynamic Result) { try { NextResultUrl = null; if (Result.TryGetValue("@odata.nextLink", out JToken JT)) { var JO = (JValue)JT; NextResultUrl = Result["@odata.nextLink"]; } if (Result.TryGetValue("value", out JToken JT2)) { foreach (dynamic Entry in Result.value) try { var val = new cCitrixApiResultBase(Entry); this.Add(val); } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); } } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); } } } public class cCitrixApiResultBase { public string ID { get; private set; } = null; public string ODataId { get; private set; } = null; public string DisplayName { get; private set; } = null; public string Context { get; private set; } = null; public dynamic Result { get; private set; } = null; public cCitrixApiResultBase(dynamic Result, string namePropery = null) { this.Result = Result; try { ID = Result.Id; } catch { } ; try { if (namePropery == null) namePropery = "displayName"; if (Result.TryGetValue(namePropery, out JToken JT1)) { var _ty = JT1.GetType().ToString(); if (JT1 is JValue _jVal) DisplayName = _jVal.Value?.ToString(); } else if (Result.TryGetValue("name", out JToken JT2)) DisplayName = Result.name; } catch { } try { Context = Result["@odata.context"]; } catch { } ODataId = GetStringFromDynamic(Result, "@odata.id"); if (string.IsNullOrEmpty(ODataId)) ODataId = @"https://api.cloud.com/" + ID; } public cCitrixApiResultBase(cCitrixApiResultBase Result) { if (Result == null) return; this.Result = Result.Result; ID = Result.ID; ODataId = Result.ODataId; DisplayName = Result.DisplayName; Context = Result.Context; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetStringFromDynamic(dynamic O, string ProperyName) { try { return (string)O[ProperyName]; } catch { } return null; } } public class cCitrixCustomerInfo { public string Id { get; set; } public string Name { get; set; } = string.Empty; public cCitrixSiteInfo PrimarySite { get; set; } public Dictionary Sites { get; private set; } = new Dictionary(); } public class cCitrixSiteInfo { public Guid Id { get; set; } public string Name { get; set; } = string.Empty; public bool? IsValid { get; set; } = null; public bool IsPrimarySite { get; set; } = false; } public class cCitrixCommunicationSystem { public const string constCitrixCloudMonitorUrl = "monitorodata/"; public const string constCitrixOnPremMonitorUrl = "Citrix/Monitor/OData/v4/Data/"; public cCitrixCommunicationMonitor Monitor { get; private set; } public cCitrixCommunicationCvad CVAD { get; private set; } public bool IsCloud { get; private set; } public string Name { get; private set; } = string.Empty; public cCitrixCommunicationSystem(cCitrixCommunicationCloudBase CitrixCloud, string Name) { IsCloud = true; this.Name = Name; this.Monitor = new cCitrixCommunicationMonitor(CitrixCloud); this.CVAD = new cCitrixCommunicationCvad(CitrixCloud); } public cCitrixCommunicationSystem(cCitrixCommunicationOnPremCvad Cvad, cCitrixCommunicationOnPremMonitor Monitor, string Name) { IsCloud = false; this.Name = Name; this.Monitor = new cCitrixCommunicationMonitor(Monitor); this.CVAD = new cCitrixCommunicationCvad(Cvad); } public async Task ScanCustomerInfosAsync(CancellationToken token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _retVal = new cCitrixCustomerInfo(); var CustomerInfo = await CVAD.GetCustomerInfoAsync(token); if (token.IsCancellationRequested) return null; if (CustomerInfo == null) return _retVal; _retVal.Id = CustomerInfo.Id; _retVal.Name = CustomerInfo.Name; cCitrixSiteInfo PrimarySite = null; int maxConsitency = 0; int countC0 = 0; int countC1 = 0; int countC2 = 0; foreach (var site in CustomerInfo.Sites.Values) { var _newSite = new cCitrixSiteInfo() { Name = site.Name, Id = site.Id, IsValid = false }; // validate the site consitency var _resultConsitency = await CheckSiteConsitencyAsync(site.Id.ToString("D"), token); if (token.IsCancellationRequested) return null; switch (_resultConsitency) { case 0: _newSite.IsValid = null; countC0++; break; case 1: _newSite.IsValid = true; countC1++; break; case 2: _newSite.IsValid = true; countC2++; break; } if (maxConsitency < _resultConsitency) PrimarySite = _newSite; maxConsitency = Math.Max(maxConsitency, _resultConsitency); _retVal.Sites.Add(_newSite.Id, _newSite); } if (PrimarySite != null) { if (countC2 == 1) PrimarySite.IsPrimarySite = true; else if (countC1 == 1 && countC2 == 0) PrimarySite.IsPrimarySite = true; } if (PrimarySite?.IsPrimarySite == true) _retVal.PrimarySite = PrimarySite; return _retVal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public async Task CheckSiteConsitencyAsync(string SiteId, CancellationToken token) { // check if the machines queried from the monitor component could be found in the cvad component // return values: // 2: all machines could be found // 1: al least one machine could be found // 0: no machines in monitor component // -1: none of the machines could be found // -2: internal error occured MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } var _lastSiteId = CVAD.CitrixInfo.SiteId; try { CVAD.CitrixInfo.SiteId = SiteId; // get the machine list from cvad var _machinesCvad = await CVAD.GetMachineNamesAsync(token); if (token.IsCancellationRequested) return -2; // get the machine list from monitor var _machinesMonitor = await Monitor.GetMachineNamesAsync(token); if (token.IsCancellationRequested) return -2; if (_machinesMonitor == null || _machinesMonitor.Count == 0) return 0; var atLeastOne = false; var all = true; foreach ( var machineName in _machinesMonitor ) { if (_machinesCvad.Contains(machineName)) atLeastOne = true; else all = false; } if (atLeastOne && all) return 2; if (atLeastOne) return 1; return -1; } catch (Exception E) { LogException(E); } finally { CVAD.CitrixInfo.SiteId = _lastSiteId; if (CM != null) LogMethodEnd(CM); } return -2; } } public interface iCitrixCvadInfo { string CustomerId { get; } string SiteId { get; set; } } public class cCitrixCommunicationCloudBase : cHttpApiBase, iCitrixCvadInfo { public const int constMaxPageSize = 1000; private string InstanceId = null; public cCitrixCommunicationCloudBase(string baseUrlCloud) : base("Citrix customer tenant", $"{baseUrlCloud}/cctrustoauth2/{{Tenant}}/tokens/clients", "Citrix API", baseUrlCloud) { MaxConcurrentRequests = 1; KnownAutoRetryErrors.Add((HttpStatusCode)429); autoRetryCount = 50; autoRetryDelay = 200; autoRetryOnKnownErrors = true; } private protected override List> GetRequestHeaders(cOAuthLogonInfo privLogonInfo) { return new List>() { new KeyValuePair("Authorization", $"CwsAuth Bearer={AccessToken}"), new KeyValuePair("Citrix-CustomerId", privLogonInfo.Tenant), new KeyValuePair("Citrix-InstanceId", InstanceId) }; } public string CustomerId { get { return this.privLogonInfo?.Tenant; } } public string SiteId { get { return InstanceId; } set { InstanceId = value; } } } public class cCitrixCommunicationOnPremCvad : cHttpApiBase, iCitrixCvadInfo { private const string constLogonUrl = "manage/Tokens"; private string privCustomerId = null; private string InstanceId = null; public cCitrixCommunicationOnPremCvad(string baseUrl) : base("Citrix on-prem system", baseUrl, "Citrix API", baseUrl + "/cvad/") { // MaxConcurrentRequests = 1; //KnownAutoRetryErrors.Add((HttpStatusCode)429); //autoRetryCount = 50; //autoRetryDelay = 200; //autoRetryOnKnownErrors = true; } public string CustomerId { get { return privCustomerId; } } public string SiteId { get { return InstanceId; } set { InstanceId = value; } } private protected override async Task privLogonWithCredentialsAsync(NetworkCredential credentials) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _usr = credentials.UserName; if (!string.IsNullOrEmpty(credentials.Domain)) _usr = $"{credentials.Domain}\\{_usr}"; var _auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{_usr}:{credentials.Password}")); using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", _auth); httpClient.BaseAddress = new Uri(RequestBaseUrl); var _result = await httpClient.PostAsync(constLogonUrl, null); if (_result != null) { var content = await _result.Content.ReadAsStringAsync(); if (_result.IsSuccessStatusCode) { var JO = JObject.Parse(content); var token = JO["Token"]?.ToString(); var CustomerId = JO["CustomerId"]?.ToString(); var strExpires = JO["ExpiresAt"]?.ToString(); if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(CustomerId) || string.IsNullOrEmpty(strExpires)) { LastErrorMessage = $"CVAD Logon failed because no valid token information could be found in the logon result."; return new cCredentialLoginInfo(); } if (!DateTime.TryParse(strExpires, out var expires)) { LastErrorMessage = $"CVAD Logon failed because no valid expiration information could be found in the logon result."; return new cCredentialLoginInfo(); } expires = DateTime.SpecifyKind(expires, DateTimeKind.Utc); var _now = DateTime.UtcNow; var _expieresInteral = expires - _now; var _delta = _expieresInteral.TotalSeconds * RelogonFactor; var expires2 = _now + TimeSpan.FromSeconds(_delta); this.privCustomerId = CustomerId; return new cCredentialLoginInfo() { IsValid = true, Token = token, TokenExpiresIn = expires2 }; } else { LastErrorMessage = $"CVAD Logon failed with status code {(int)_result.StatusCode}: {_result.ReasonPhrase}"; } } else LastErrorMessage = $"CVAD Logon failed due to unknown reason."; } } catch (Exception E) { LastErrorMessage = $"CVAD Logon failed with exception: {E.Message}"; LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return new cCredentialLoginInfo(); } private protected override List> GetRequestHeaders(cOAuthLogonInfo privLogonInfo) { var _retVal = new List>() { new KeyValuePair("Authorization", $"CwsAuth Bearer={AccessToken}") }; if (privCustomerId != null) _retVal.Add(new KeyValuePair("Citrix-CustomerId", privCustomerId)); if (InstanceId != null) _retVal.Add(new KeyValuePair("Citrix-InstanceId", InstanceId)); return _retVal; } } public class cCitrixCommunicationOnPremMonitor : cHttpApiBase { private const string constLogonUrl = "Users?$top=1&$select=Id"; public cCitrixCommunicationOnPremMonitor(string baseUrl) : base("Citrix on-prem system", baseUrl, "Citrix API", baseUrl + string.Format("/Citrix/Monitor/OData/v4/Data/")) { } private protected override async Task privLogonWithCredentialsAsync(NetworkCredential credentials) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{credentials.Domain}\\{credentials.UserName}:{credentials.Password}")); using (var _handler = new HttpClientHandler()) { _handler.Credentials = credentials; _handler.PreAuthenticate = true; using (var httpClient = new HttpClient(_handler)) { httpClient.BaseAddress = new Uri(RequestBaseUrl); httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); var _result = await httpClient.GetAsync(constLogonUrl); if (_result != null) { var content = await _result.Content.ReadAsStringAsync(); if (_result.IsSuccessStatusCode) return new cCredentialLoginInfo() { IsValid = true, Token = null, TokenExpiresIn = DateTime.MaxValue, useAlwaysCredentials = true }; else LastErrorMessage = $"Monitor Logon failed with status code {(int)_result.StatusCode}: {_result.ReasonPhrase}"; } else LastErrorMessage = $"Monitor Logon failed due to unknown reason."; } } } catch (Exception E) { LastErrorMessage = $"Monitor Logon failed with exception: {E.Message}"; LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return new cCredentialLoginInfo(); } private protected override List> GetRequestHeaders(cOAuthLogonInfo privLogonInfo) { var _retVal = new List>(); { new KeyValuePair("Accept", "application/json"); } ; return _retVal; } } public class cCitrixCommunicationBase { private cHttpApiBase HttpApi; public Uri baseUri { get; private set; } public string LastErrorMessage { get { return HttpApi.LastErrorMessage; } } public cCitrixCommunicationBase(cHttpApiBase HttpApi, string baseUrl) { this.HttpApi = HttpApi; this.baseUri = new Uri(new Uri(HttpApi.RequestBaseUrl), baseUrl); } public async Task RequestAsync(string Request, eHttpMethod httpMethod, object JsonData, string nameProperty = null, bool retryForbidden = false, bool noAutoRelogon = false, CancellationToken token = default) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var uriRequest = new Uri(baseUri, Request); var data = await HttpApi.privRequestAsync(uriRequest.OriginalString, httpMethod, JsonData, retryForbidden, noAutoRelogon: noAutoRelogon, token: token); if (data == null) return null; if (data is bool isValid) { if (isValid) return new cCitrixApiResultBase(null); else return null; } var RetVal = new cCitrixApiResultBase(data, nameProperty); return RetVal; } catch (Exception E) { cLogManager.LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public async Task RequestListAsync(string Request, bool loadPaged, bool retryForbidden, CancellationToken token = default) { try { var uriRequest = new Uri(baseUri, Request); var res = await HttpApi.privRequestAsync(uriRequest.OriginalString, autoRetry: retryForbidden, token: token); if (res != null) { var RetVal = new cCitrixApiResultList(retryForbidden); RetVal.AddResult(res); if (!RetVal.hasRemaining || loadPaged) return RetVal; while (RetVal.hasRemaining) { if (!await RequestNextAsync(RetVal)) return RetVal; } return RetVal; } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); } return null; } public async Task RequestNextAsync(cCitrixApiResultList List, CancellationToken token = default) { try { var res = await HttpApi.privRequestAsync(List.NextResultUrl, autoRetry: List.retryForbidden, token: token); if (res != null) { List.AddResult(res); return true; } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); } return false; } } public class cCitrixCommunicationCvad { public const string constCitrixCvadUrl = "cvad/"; public const string constCitrixApiMe = "manage/Me"; public const string constCitrixApiMachineNames = "manage/Machines?fields=DnsName"; public const string constCitrixApiSessions = "manage/Sessions?fields=Id,Uid,State,StartTime"; public cCitrixCommunicationBase Request { get; private set; } public iCitrixCvadInfo CitrixInfo { get; private set; } public cCitrixCommunicationCvad(cCitrixCommunicationCloudBase CitrixCloud) { this.Request = new cCitrixCommunicationBase(CitrixCloud, constCitrixCvadUrl); this.CitrixInfo = CitrixCloud as iCitrixCvadInfo; } public cCitrixCommunicationCvad(cCitrixCommunicationOnPremCvad OnPremCvad) { this.Request = new cCitrixCommunicationBase(OnPremCvad, null); this.CitrixInfo = OnPremCvad as iCitrixCvadInfo; } public string GetServer() { return Request?.baseUri?.Host; } public async Task GetCustomerInfoAsync(CancellationToken Token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _resultMe = await Request.RequestAsync(constCitrixApiMe, eHttpMethod.get, null); if (Token.IsCancellationRequested) return null; if (_resultMe?.Result == null) return null; var _obj = _resultMe.Result.Customers; var _customers = _obj as IEnumerable; if (_customers == null) return null; cCitrixCustomerInfo _retVal = null; foreach (var _customer in _customers) { var id = _customer.Id; if (id != CitrixInfo?.CustomerId) continue; _retVal = new cCitrixCustomerInfo() { Id = _customer.Id, Name = _customer?.Name }; _obj = _customer.Sites; var _sites = _obj as IEnumerable; if (_sites != null) foreach (var _site in _sites) { try { if (Guid.TryParse(_site.Id.ToString(), out Guid _id)) { var siteInfo = new cCitrixSiteInfo() { Id = _id, Name = _site.Name }; _retVal.Sites[_id] = siteInfo; } } catch (Exception E) { cLogManager.DefaultLogger.LogException(E); } } } return _retVal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public async Task> GetMachineNamesAsync(CancellationToken token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _result = await Request.RequestAsync(constCitrixApiMachineNames, eHttpMethod.get, null, token: token); if (_result?.Result == null) return null; var _items = _result.Result?.Items as IEnumerable; if (_items == null) return null; var _retVal = new List(); foreach ( var item in _items ) { try { var _dnsName = item.DnsName?.ToString()?.ToLowerInvariant(); if (!string.IsNullOrEmpty( _dnsName )) _retVal.Add( _dnsName ); } catch (Exception E) { LogException(E); } } return _retVal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public class SessionInfo { public string Id { get; set; } public string Uid { get; set; } public string State { get; set; } public DateTime? StartTime { get; set; } } public async Task> GetSessionsAsync() { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var _result = await Request.RequestAsync(constCitrixApiSessions, eHttpMethod.get, null); if (_result?.Result == null) return null; var _items = _result.Result?.Items as IEnumerable; if (_items == null) return null; var _retVal = new List(); foreach (var item in _items) { try { var _sessionInfo = new SessionInfo() { Id = item.Id?.ToString(), Uid = item.Uid?.ToString(), State = item.State?.ToString(), StartTime = item.StartTime != null ? (DateTime?)item.StartTime : null }; _retVal.Add(_sessionInfo); } catch (Exception E) { LogException(E); } } return _retVal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } } public class cCitrixCommunicationMonitor { public const string constCitrixCloudUrl = "monitorodata/"; public const string constCitrixMachineNames = "Machines/?$select=DnsName&$filter=DesktopGroupId+ne+null+and+Name+ne+null"; public const string constCitrixUserList = "Users?$select=Id,Sid,Upn,Domain&$filter=Domain ne ''"; public const string constCitrixMachineList = "Machines?$select=Id,Sid,DnsName&$filter=DesktopGroupId ne null and Name ne null"; public const string constCitrixSessionListDetailed = "Sessions?$select=SessionKey,StartDate,EndDate,ConnectionState, UserId, MachineId&$expand=Machine($select=Id,DnsName,Name)&$filter=EndDate eq null or EndDate gt {0}"; public int MaxPageSize { get; set; } = 100; public cCitrixCommunicationBase Request { get; private set; } public cCitrixCommunicationMonitor(cCitrixCommunicationCloudBase CitrixCloud) { this.Request = new cCitrixCommunicationBase(CitrixCloud, constCitrixCloudUrl); } public cCitrixCommunicationMonitor(cCitrixCommunicationOnPremMonitor OnPremMonitor) { this.Request = new cCitrixCommunicationBase(OnPremMonitor, null); } private string getFormattedDateTime(int DayOffset = 0) { var _dateTime = DateTime.UtcNow.AddDays(DayOffset); var _strDateTime = _dateTime.ToString("yyyy-MM-ddTHH:mm:ssZ"); return _strDateTime; } private string getFormattedGuid(Guid id) { return id.ToString("D"); } public async Task> GetMachineNamesAsync(CancellationToken token) { var _processor = new ScanEntryStringListProcessor("DnsName", true); var _count = await RequestListAsync(constCitrixMachineList, _processor.ProcessScannedItemAsync, token: token); return _processor.Result; } public delegate Task ProcessScanEntryDelegate(dynamic ScanEntry, CancellationToken token); public abstract class ScanEntryProcessor { public abstract Task ProcessScannedItemAsync(dynamic _node, CancellationToken token); } public class ScanEntryCountProcessor : ScanEntryProcessor { private string nameProperty = null; public ScanEntryCountProcessor(string nameProperty) { this.nameProperty = nameProperty; } public async override Task ProcessScannedItemAsync(dynamic _node, CancellationToken token) { try { string _prop = cCitrixApiResultBase.GetStringFromDynamic(_node, nameProperty); if (!string.IsNullOrEmpty(_prop)) return true; } catch (Exception E) { LogException(E); } return false; } } public class ScanEntryStringListProcessor : ScanEntryProcessor { private string nameProperty = null; private bool toLowercase = false; public List Result = new List(); public ScanEntryStringListProcessor(string nameProperty, bool toLowercase = false) { this.nameProperty = nameProperty; this.toLowercase = toLowercase; } public async override Task ProcessScannedItemAsync(dynamic _node, CancellationToken token) { try { string _prop = cCitrixApiResultBase.GetStringFromDynamic(_node, nameProperty); if (!string.IsNullOrEmpty(_prop)) { if (toLowercase) _prop = _prop.ToLowerInvariant(); Result.Add(_prop); return true; } } catch (Exception E) { LogException(E); } return false; } } private async Task RequestListAsync(string Url, ProcessScanEntryDelegate ProcessScanEntry, CancellationToken token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { int countTotal = 0; int skipping = 0; int lastCount = 0; do { lastCount = 0; var _url = Url + $"&$top={MaxPageSize}&$skip={skipping}"; var _result = await Request.RequestAsync(_url, eHttpMethod.get, null, token: token); if (_result == null || _result.Result == null) return -1; var _items = _result.Result?.value as IEnumerable; if (_items == null) return -1; foreach (var item in _items) { try { var _item = item as dynamic; var _ok = await ProcessScanEntry(_item, token); if (token.IsCancellationRequested) return 0; if (_ok) { lastCount++; countTotal++; } } catch (Exception E) { LogException(E); } } skipping += lastCount; } while (lastCount >= MaxPageSize); return countTotal; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return -1; } public async Task ScanUsersAsync(ScanEntryProcessor _processor, CancellationToken token) { return await RequestListAsync(constCitrixUserList, _processor.ProcessScannedItemAsync, token); } public async Task ScanMachinesAsync(ScanEntryProcessor _processor, CancellationToken token) { return await RequestListAsync(constCitrixMachineList, _processor.ProcessScannedItemAsync, token); } public async Task ScanSessionsAsync(ScanEntryProcessor _processor, int DayOffset, CancellationToken token) { var _url = string.Format(constCitrixSessionListDetailed, getFormattedDateTime(DayOffset)); return await RequestListAsync(_url, _processor.ProcessScannedItemAsync, token); } } }