@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1,034 lines (1,031 loc) • 45.1 kB
JavaScript
import { EMPTY, Observable, throwError } from 'rxjs';
import { catchError, expand, mergeMap, tap } from 'rxjs/operators';
import { LogLevel } from '../diagnostics/log-level';
import { Logging } from '../diagnostics/logging';
import { SmeWebTelemetry } from '../diagnostics/sme-web-telemetry';
import { TelemetryEventStates } from '../diagnostics/sme-web-telemetry-models';
import { Disposer } from './disposable';
import { headerConstants } from './http-constants';
import { Net } from './net';
/**
* The PowerShellSession class.
*/
export class PowerShellSession {
powerShell;
lifetime;
constructor(powerShell, lifetime) {
this.powerShell = powerShell;
this.lifetime = lifetime;
}
/**
* Gets the node name of session.
*/
get nodeName() {
return this.powerShell.nodeName;
}
/**
* Dispose the session object.
*/
dispose() {
if (this.lifetime) {
this.lifetime.dispose();
}
}
}
/**
* Class containing methods related to PowerShell runspace creation/deletion/command using PowerShell Raw API plugin.
* - It's auto holding the session as long as it's used within last 3 minutes.
*/
export class PowerShellRaw {
nodeConnection;
context;
// 3 minutes holding time.
static maxDeltaTimeInMs = 3 * 60 * 1000;
sessionId;
timestampInMs = 0;
markDelete = false;
internalActive = false;
cancelPending = false;
/**
* Initializes a new instance of the PowerShellRaw class.
*
* @param nodeConnection The node connection service.
* @param context The context of PowerShell run.
*/
constructor(nodeConnection, context) {
this.nodeConnection = nodeConnection;
this.context = context;
}
/**
* Gets active status of PowerShell execution.
*/
get active() {
return this.internalActive;
}
/**
* Dispose the runspace.
*/
dispose() {
if (!this.active) {
// only close sessions that have been created.
// If a result was cached a component may not
// execute a command and still dispose the session
// when the component is destroyed.
if (this.sessionId) {
this.close();
}
}
else {
this.markDelete = true;
}
}
/**
* Runs the given command
*
* @param command The command to execute.
* @param options the powershell options.
*/
runCommand(command, options) {
// take the timestamp only success/healthy case.
// error session would be auto-deleted after expiration time.
this.internalActive = true;
return this.command(command, options)
.pipe(catchError((error) => this.fallbackToJea(error, command, options)), expand((data) => {
this.timestampInMs = Date.now();
if (this.checkCompleted(data)) {
return EMPTY;
}
if (this.cancelPending) {
// submit cancel request.
// after set active state to false and complete the observable.
this.cancelPending = false;
return this.cancel()
.pipe(catchError(() => {
this.internalActive = false;
return EMPTY;
}), mergeMap(() => {
this.internalActive = false;
return EMPTY;
}));
}
const url = Net.powerShellApiRetrieveOutput.format(this.sessionId);
return this.nodeConnection.get(this.context.nodeName, url, this.context.requestOptions)
.pipe(catchError((error) => {
SmeWebTelemetry.tracePowershellEvent(command, TelemetryEventStates.Error, { response: error.response });
return this.fallbackToJea(error, command, options);
}));
}));
}
/**
* Close/Delete the session / runspace.
*/
close() {
if (this.context.requestOptions.automatic) {
return;
}
if (this.sessionId) {
const sessionUri = Net.powerShellApiSessions.format(this.sessionId);
this.sessionId = null;
this.nodeConnection.deleteQuick(this.context.nodeName, sessionUri, this.context.requestOptions);
return;
}
Logging.log({
level: LogLevel.Verbose,
source: 'PowerShell/close',
message: MsftSme.getStrings().MsftSmeShell.Core.Error.PowerShellUnableSessionClose.message
});
}
/**
* Cancel the command.
*/
cancelCommand() {
if (this.internalActive) {
this.cancelPending = true;
}
return EMPTY;
}
/**
* Perform the JEA fallback, if applicable.
*
* @param error The error to handle
* @param command The command
* @param options The request options
*/
fallbackToJea(error, command, options) {
const authError = Net.isUnauthorized(error) || error.status === 400 /* HttpStatusCode.BadRequest */;
const responseEndpoint = error && error.xhr && error.xhr.getResponseHeader(headerConstants.POWERSHELL_ENDPOINT);
let requestEndpoint = (options && options.powerShellEndpoint);
requestEndpoint = requestEndpoint || (this.context.requestOptions && this.context.requestOptions.powerShellEndpoint);
const cancel = error.handlerError && error.handlerError.code && error.handlerError.code === 'ManageAsDialogCancel';
const credSSP = (options && options.authenticationMechanism === 'Credssp');
if (!cancel && authError && responseEndpoint && requestEndpoint !== responseEndpoint && !credSSP) {
this.context.requestOptions.powerShellEndpoint = responseEndpoint;
return this.command(command, options)
.pipe(tap(() => {
// The JEA request went through - persist this context in authorization manager.
this.nodeConnection.saveJeaContext(this.context.nodeName, responseEndpoint);
}));
}
// close on error if sessionId is available.
if (options && options.closeOnError) {
if (!this.sessionId) {
this.sessionId = error && error.xhr && error.xhr.response && error.xhr.response.sessionId;
}
if (this.sessionId) {
this.close();
}
}
this.internalActive = false;
return throwError(() => error);
}
cancel() {
if (this.sessionId && this.internalActive) {
const cancelUri = Net.powerShellApiCancelCommand.format(this.sessionId);
return this.nodeConnection.post(this.context.nodeName, cancelUri, null, this.context.requestOptions);
}
Logging.log({
level: LogLevel.Warning,
source: 'PowerShell',
message: MsftSme.getStrings().MsftSmeShell.Core.Error.PowerShellUnableCancelCommand.message
});
return EMPTY;
}
/**
* Gets if timestamp was expired.
*/
get _isExpired() {
const now = Date.now();
return this.timestampInMs !== 0 && (now - this.timestampInMs) > PowerShellRaw.maxDeltaTimeInMs;
}
/**
* Initiate command execution. It auto recycles old sessions.
*
* @param command the PowerShell command.
*/
command(command, options) {
const commandPacket = { ...command };
commandPacket.useInProcRunspace = !!options?.useInProcRunspace;
const polling = !!this.context.requestOptions.automatic;
if (polling) {
commandPacket.invokeMode = 'Polling';
}
if (options && options.waitTimeMs) {
commandPacket.waitTimeMs = options.waitTimeMs;
}
const data = Net.createPropertiesJSONString(commandPacket);
const newOptions = {
...this.context.requestOptions,
...{
logAudit: options && options.logAudit,
logTelemetry: options && options.logTelemetry
}
};
const endpoint = options && options.powerShellEndpoint;
if (endpoint) {
newOptions.powerShellEndpoint = endpoint;
}
const token = options && options.authToken;
if (token) {
newOptions.authToken = token;
}
const authenticationMechanism = options && options.authenticationMechanism;
if (authenticationMechanism) {
newOptions.authenticationMechanism = authenticationMechanism;
}
if (newOptions.logTelemetry && !polling) {
SmeWebTelemetry.tracePowershellEvent(command, TelemetryEventStates.Started);
}
let commandResponse;
if (polling) {
this.sessionId = null;
commandResponse = this.nodeConnection.post(this.context.nodeName, Net.powerShellApiInvokeCommand, data, newOptions);
}
else if (this.sessionId == null || this._isExpired) {
this.sessionId = null;
const generatedName = this.context.key ? this.context.key + '-newSession' : 'instantSession';
const sessionUri = Net.powerShellApiSessions.format(generatedName);
commandResponse = this.nodeConnection.put(this.context.nodeName, sessionUri, data, newOptions);
}
else {
const executeUri = Net.powerShellApiExecuteCommand.format(this.sessionId);
commandResponse = this.nodeConnection.post(this.context.nodeName, executeUri, data, newOptions);
}
return commandResponse.pipe(catchError((error) => {
SmeWebTelemetry.tracePowershellEvent(command, TelemetryEventStates.Error, { response: error.response });
return throwError(() => error);
}));
}
checkCompleted(data) {
const properties = Net.getItemProperties(data);
if (properties.sessionId) {
// keep the PS session GUID
this.sessionId = properties.sessionId;
}
if (properties.completed.toLowerCase() === 'true') {
this.internalActive = false;
if (this.markDelete) {
this.close();
}
return true;
}
return false;
}
}
/**
* The PowerShell class.
*
* - Single instance of PowerShell class manages single runspace.
* - It queues coming requests and process one at a time sequentially.
* - If a command is slow and causing with multiple responses, it aggregates response into single Q result.
* - A PowerShell instance should be created through create() function, and it's statically stored/managed into _map collection.
* - In QueryCache operation, it can find the PowerShell instance to run PowerShell command by using find() function.
* - Once all lifetime references are gone, it deletes the runspace.
* - To dispose the PowerShell instance, it can use lifetime.dispose().
*/
export class PowerShell {
/**
* Default PowerShell endpoint.
*/
static defaultPowerShellEndpoint = 'http://schemas.microsoft.com/powershell/microsoft.powershell';
/**
* SME PowerShell endpoint.
*/
static smePowerShellEndpoint = 'http://schemas.microsoft.com/powershell/microsoft.sme.powershell';
/**
* WAC (v2) CredSSP PowerShell endpoint to control client role of CredSSP on the gateway.
*/
static wacCredSSPEndpoint = 'http://schemas.microsoft.com/powershell/Microsoft.WindowsAdminCenter.Credssp';
/**
* Static collection of PowerShell objects.
*/
static map = {};
/**
* Regular expression to match all the occurrences of a single quote
*/
static escapeRegex = new RegExp('\'', 'g');
/**
* The context of PowerShell object.
*/
context;
/**
* The queue of PowerShell command requests.
*/
queue = [];
/**
* The reference to PowerShellRaw class object.
*/
raw;
/**
* Current data to aggregate from multiple data responses.
*/
currentData;
/**
* Timestamp when last command started.
*/
timestamp;
/**
* Create script as string.
* (Notes: Use createCommand() function which is based on PowerShell module,
* Update gulpfile.js to generate a PowerShell module to support Show script, JEA and localization.)
*
* @param resource the script text from legacy ps-code converter.
* @param parameters the arguments.
* @param flags (optional) the switch flags.
*/
static createScript(script, parameters, flags) {
script = 'function cvt ($o) { return ConvertFrom-Json $o }\n function SmeSubmit {\n' + script + '}\n SmeSubmit';
for (const parameter in parameters) {
if (parameters.hasOwnProperty(parameter)) {
const value = parameters[parameter];
if (value == null) {
script += ' -{0} $null'.format(parameter);
}
else {
script += ' -{0} (cvt \'{1}\')'.format(parameter, JSON.stringify(value).replace(PowerShell.escapeRegex, '\'\''));
}
}
}
if (flags) {
for (let i = 0; i < flags.length; i++) {
script += ' -{0}'.format(flags[i]);
}
}
return script;
}
/**
* Create PowerShell request command.
* (It creates a command object of JEA PowerShell request under restricted user role environment.)
*
* @param resource the script resource object with command and script data from new ps-code converter.
* @param parameters the arguments.
* @param flags (optional) the switch flags.
* @return PowerShellCommand the PowerShell request command object.
*/
static createCommand(resource, parameters, flags, resourceName) {
// step1: Add Jea prefix dynamically
const powerShellPrefix = MsftSme.self().Init.powerShellPrefix;
let command = resource.command;
if (powerShellPrefix && resource && resource.command) {
command = PowerShell.addPowerShellPrefix(powerShellPrefix, resource.command);
}
// step2: Adding parameters converter from JSON to PowerShell.
// step3: Surround full content into SmeSubmit function.
let script = 'function cvt($o){return ConvertFrom-Json $o}\n function SmeSubmit{\n' + resource.script + '}\nSmeSubmit';
// step4: Adding localized resources strings overriding Import-LocalizedData as function.
if (resourceName) {
const strings = MsftSme.getStrings()[resourceName];
if (strings && strings['PowerShell']) {
const items = strings['PowerShell'];
const keys = Object.keys(items);
const rightSingleQuotationMark = '\u2019';
const lines = keys.map(key => (key + '=\''
+ items[key]
.split(rightSingleQuotationMark)
.join('\'\'')
.split('\'')
.join('\'\'')
+ '\'\n'));
script = 'function Import-LocalizedData{$script:strings=@{\n' + lines.join('') + '}}' + script;
}
}
// step5: Adding each parameter with using converter.
for (const parameter in parameters) {
if (parameters.hasOwnProperty(parameter)) {
const value = parameters[parameter];
if (value == null) {
script += ' -{0} $null'.format(parameter);
}
else {
script += ' -{0} (cvt \'{1}\')'.format(parameter, JSON.stringify(value).replace(PowerShell.escapeRegex, '\'\''));
}
}
}
// step6: Adding switch parameters.
const flagParameters = {};
if (flags) {
for (let i = 0; i < flags.length; i++) {
script += ' -{0}'.format(flags[i]);
flagParameters[flags[i]] = true;
}
}
return {
module: resource.module,
command,
parameters: { ...flagParameters, ...parameters },
script,
state: 'ready'
};
}
/**
* Update the parameters in the PowerShellCommand object, and update the SmeSubmit part of the
* script with these new parameters.
*
* @param command The PowerShellCommand instance to update.
* @param parameters The new collection of parameters.
*
* Note: flags support can be added when it becomes necessary.
*/
static updateCommandParameters(command, parameters) {
for (const parameter in parameters) {
if (parameters.hasOwnProperty(parameter)) {
command.parameters[parameter] = parameters[parameter];
const value = parameters[parameter];
// Regular expression to capture existing parameter.
const regex = new RegExp('(?<=}\\s+SMESubmit .*)-{0} (\\(cvt.+?\\)|\\$null)(?= -|$)'.format(parameter), 'i');
if (command.script.match(regex)) {
if (value == null) {
command.script = command.script.replace(regex, ' -{0} $null'.format(parameter));
}
else {
command.script = command.script.replace(regex, ' -{0} (cvt \'{1}\')'.format(parameter, JSON.stringify(value).replace(PowerShell.escapeRegex, '\'\'')));
}
}
else {
command.script += ' -{0} (cvt \'{1}\')'.format(parameter, JSON.stringify(value).replace(PowerShell.escapeRegex, '\'\''));
}
}
}
}
static create(nodeName, nodeConnection, key, lifetime, requestOptions) {
let ps;
if (key && lifetime) {
ps = PowerShell.map[PowerShell.indexName(nodeName, key)];
if (ps) {
ps.addLifetime(lifetime);
return ps;
}
}
ps = new PowerShell(nodeName, nodeConnection, key, lifetime, requestOptions);
if (key && lifetime) {
PowerShell.map[PowerShell.indexName(nodeName, key)] = ps;
}
return ps;
}
/**
* Find existing PowerShell object. Create call must be called before to create the PowerShell instance.
*
* @param nodeName The node name.
* @param key The shared key to queue the requests to use the single runspace.
*/
static find(nodeName, key) {
return PowerShell.map[PowerShell.indexName(nodeName, key)];
}
/**
* Gets the command object from string or PowerShellCommand.
*
* @param scriptOrCommand the script string or PowerShellCommand object.
*/
static getPowerShellCommand(scriptOrCommand) {
return typeof scriptOrCommand === 'string' ?
{
script: scriptOrCommand,
command: null,
module: null,
state: 'ready'
}
: {
script: scriptOrCommand.script,
command: scriptOrCommand.command,
module: scriptOrCommand.module || MsftSme.self().Init.powerShellModuleName,
parameters: scriptOrCommand.parameters,
state: 'ready'
};
}
/**
* Create new options with debugging endpoint if requested.
*
* @param options the PowerShell session request options.
*/
static newEndpointOptions(options) {
// if there is no endpoint but configured with powerShellEndpoint, set debugging endpoint.
const newOptions = { ...(options || {}) };
if (!newOptions.powerShellEndpoint && MsftSme.self().Init.powerShellEndpoint) {
newOptions.powerShellEndpoint = MsftSme.self().Init.powerShellEndpoint;
}
return newOptions;
}
/**
* Create the index name in map collection.
*
* @param nodeName The node name.
* @param key The shared key to queue the requests to use the single runspace.
*/
static indexName(nodeName, key) {
return nodeName + ':' + key;
}
/**
* Adds jea prefix to the command name
*
* @param jeaPrefix The jea prefix originating from main.ts.
* @param command The powershell command to run.
*/
static addPowerShellPrefix(powerShellPrefix, command) {
const hyphenSeparatorIndex = command.indexOf('-');
const verb = command.substring(0, hyphenSeparatorIndex);
const target = command.substring(hyphenSeparatorIndex + 1);
if (target.indexOf(powerShellPrefix) === 0) {
throw new Error('Command already contains prefix');
}
return verb + '-' + powerShellPrefix + target;
}
/**
* Initializes a new instance of the PowerShell class.
* (private constructor which shouldn't be called directly.)
*
* @param nodeConnection The node connection service.
* @param key The shared key to queue the requests to use the single runspace.
* @param lifetime The lifetime container.
*/
constructor(nodeName, nodeConnection, key, lifetime, options) {
this.context = {
nodeName: nodeName,
key: key,
lifetimes: [],
requestOptions: PowerShell.newEndpointOptions(options)
};
this.timestamp = 0;
this.raw = new PowerShellRaw(nodeConnection, this.context);
if (key && lifetime) {
lifetime.registerForDispose(new Disposer(() => this.lifetimeDisposer(lifetime)));
this.context.lifetimes.push(lifetime);
}
}
/**
* Gets node name from current context.
*/
get nodeName() {
return this.context.nodeName;
}
/**
* Run PowerShell command.
*
* @param command The command.
* @param options The options.
* @return PromiseV The result of PowerShell command.
*/
run(scriptOrCommand, options) {
const command = PowerShell.getPowerShellCommand(scriptOrCommand);
if (this.context.lifetimes.length === 0) {
// no disposer is assigned, force to close the session after every query.
const timeoutMs = options && options.timeoutMs;
if (options) {
options.timeoutMs = timeoutMs;
options.close = true;
}
else {
options = { timeoutMs: timeoutMs, close: true };
}
}
// queue the request.
const observable = this.enqueue(command, options);
return observable;
}
/**
* Cancel PowerShell command.
*/
cancel() {
return this.raw.cancelCommand();
}
/**
* Enqueue a command request.
*
* @param command The command.
* @param options The options.
*/
enqueue(command, options) {
return new Observable((observer) => {
this.queue.push({ observer, command, options });
this.dequeue();
});
}
/**
* Dequeue a command request.
*/
dequeue() {
if (this.raw.active) {
return false;
}
const item = this.queue.shift();
if (item) {
this.currentData = null;
this.timestamp = Date.now();
this.raw.runCommand(item.command, item.options).subscribe({
next: data => {
const properties = Net.getItemProperties(data);
this.collect(properties, item.options && item.options.timeoutMs, item.options && item.options.partial ? item.observer : null);
},
error: error => {
if (item.options && item.options.close) {
this.raw.close();
}
item.observer.error(error);
this.timestamp = 0;
this.dequeue();
},
complete: () => {
if (item.options && item.options.close) {
this.raw.close();
}
if (!item.options || !item.options.partial) {
item.observer.next(this.currentData);
}
item.observer.complete();
this.timestamp = 0;
this.dequeue();
}
});
return true;
}
return false;
}
/**
* Collect response result and aggregate into single object.
*
* @param properties The properties of response object.
* @param timeoutMs The timeout to cancel command.
* @param observer The observer of powershell results.
*/
collect(properties, timeoutMs, observer) {
if (timeoutMs && this.timestamp && (Date.now() - this.timestamp > timeoutMs)) {
// force to cancel the command because of unexpected longer execution.
this.raw.cancelCommand();
this.timestamp = 0;
return;
}
if (observer) {
// return partial data if observer is not null.
observer.next(properties);
this.currentData = properties;
return;
}
if (this.currentData != null && this.currentData.results && properties.results) {
let array;
if (MsftSme.getTypeOf(this.currentData.results) === 'array') {
array = this.currentData.results;
}
else {
array = [this.currentData.results];
}
if (MsftSme.getTypeOf(properties.results) === 'array') {
properties.results.forEach((x) => {
array.push(x);
});
}
else {
array.push(properties.results);
}
this.currentData.results = array;
return;
}
this.currentData = properties;
}
/**
* Attach lifetime object to disposer when disposing.
*
* @param lifetime The lifetime object.
*/
addLifetime(lifetime) {
const found = MsftSme.find(this.context.lifetimes, (value) => value === lifetime);
if (!found) {
this.context.lifetimes.push(lifetime);
lifetime.registerForDispose(new Disposer(() => this.lifetimeDisposer(lifetime)));
}
}
/**
* Callback when disposing the container of view model.
* If none, reference the PowerShell object. Dispose it. (Delete runspace)
*
* @param lifetime The lifetime object.
*/
lifetimeDisposer(lifetime) {
const found = MsftSme.find(this.context.lifetimes, (value) => value === lifetime);
if (found) {
MsftSme.remove(this.context.lifetimes, lifetime);
if (this.context.lifetimes.length === 0) {
// cancel queue command requests.
this.queue.forEach((value) => {
value.observer.next(null);
value.observer.complete();
});
// delete from the map collection and delete the runspace/session.
delete PowerShell.map[PowerShell.indexName(this.context.nodeName, this.context.key)];
this.raw.dispose();
}
}
}
}
//# sourceMappingURL=powershell.js.map
// SIG // Begin signature block
// SIG // MIIoKAYJKoZIhvcNAQcCoIIoGTCCKBUCAQExDzANBglg
// SIG // hkgBZQMEAgEFADB3BgorBgEEAYI3AgEEoGkwZzAyBgor
// SIG // BgEEAYI3AgEeMCQCAQEEEBDgyQbOONQRoqMAEEvTUJAC
// SIG // AQACAQACAQACAQACAQAwMTANBglghkgBZQMEAgEFAAQg
// SIG // KL/Mg9owo/aysH1dO62lrmCgrM0rkXHg8I26mXvQgt+g
// SIG // gg12MIIF9DCCA9ygAwIBAgITMwAABARsdAb/VysncgAA
// SIG // AAAEBDANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29kZSBT
// SIG // aWduaW5nIFBDQSAyMDExMB4XDTI0MDkxMjIwMTExNFoX
// SIG // DTI1MDkxMTIwMTExNFowdDELMAkGA1UEBhMCVVMxEzAR
// SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
// SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
// SIG // bjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
// SIG // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
// SIG // tCg32mOdDA6rBBnZSMwxwXegqiDEUFlvQH9Sxww07hY3
// SIG // w7L52tJxLg0mCZjcszQddI6W4NJYb5E9QM319kyyE0l8
// SIG // EvA/pgcxgljDP8E6XIlgVf6W40ms286Cr0azaA1f7vaJ
// SIG // jjNhGsMqOSSSXTZDNnfKs5ENG0bkXeB2q5hrp0qLsm/T
// SIG // WO3oFjeROZVHN2tgETswHR3WKTm6QjnXgGNj+V6rSZJO
// SIG // /WkTqc8NesAo3Up/KjMwgc0e67x9llZLxRyyMWUBE9co
// SIG // T2+pUZqYAUDZ84nR1djnMY3PMDYiA84Gw5JpceeED38O
// SIG // 0cEIvKdX8uG8oQa047+evMfDRr94MG9EWwIDAQABo4IB
// SIG // czCCAW8wHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYB
// SIG // BQUHAwMwHQYDVR0OBBYEFPIboTWxEw1PmVpZS+AzTDwo
// SIG // oxFOMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQLExVNaWNy
// SIG // b3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAx
// SIG // Mis1MDI5MjMwHwYDVR0jBBgwFoAUSG5k5VAF04KqFzc3
// SIG // IrVtqMp1ApUwVAYDVR0fBE0wSzBJoEegRYZDaHR0cDov
// SIG // L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWlj
// SIG // Q29kU2lnUENBMjAxMV8yMDExLTA3LTA4LmNybDBhBggr
// SIG // BgEFBQcBAQRVMFMwUQYIKwYBBQUHMAKGRWh0dHA6Ly93
// SIG // d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWlj
// SIG // Q29kU2lnUENBMjAxMV8yMDExLTA3LTA4LmNydDAMBgNV
// SIG // HRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQCI5g/S
// SIG // KUFb3wdUHob6Qhnu0Hk0JCkO4925gzI8EqhS+K4umnvS
// SIG // BU3acsJ+bJprUiMimA59/5x7WhJ9F9TQYy+aD9AYwMtb
// SIG // KsQ/rst+QflfML+Rq8YTAyT/JdkIy7R/1IJUkyIS6srf
// SIG // G1AKlX8n6YeAjjEb8MI07wobQp1F1wArgl2B1mpTqHND
// SIG // lNqBjfpjySCScWjUHNbIwbDGxiFr93JoEh5AhJqzL+8m
// SIG // onaXj7elfsjzIpPnl8NyH2eXjTojYC9a2c4EiX0571Ko
// SIG // mhENF3RtR25A7/X7+gk6upuE8tyMy4sBkl2MUSF08U+E
// SIG // 2LOVcR8trhYxV1lUi9CdgEU2CxODspdcFwxdT1+G8YNc
// SIG // gzHyjx3BNSI4nOZcdSnStUpGhCXbaOIXfvtOSfQX/UwJ
// SIG // oruhCugvTnub0Wna6CQiturglCOMyIy/6hu5rMFvqk9A
// SIG // ltIJ0fSR5FwljW6PHHDJNbCWrZkaEgIn24M2mG1M/Ppb
// SIG // /iF8uRhbgJi5zWxo2nAdyDBqWvpWxYIoee/3yIWpquVY
// SIG // cYGhJp/1I1sq/nD4gBVrk1SKX7Do2xAMMO+cFETTNSJq
// SIG // fTSSsntTtuBLKRB5mw5qglHKuzapDiiBuD1Zt4QwxA/1
// SIG // kKcyQ5L7uBayG78kxlVNNbyrIOFH3HYmdH0Pv1dIX/Mq
// SIG // 7avQpAfIiLpOWwcbjzCCB3owggVioAMCAQICCmEOkNIA
// SIG // AAAAAAMwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYT
// SIG // AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
// SIG // EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
// SIG // cG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290
// SIG // IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTEx
// SIG // MDcwODIwNTkwOVoXDTI2MDcwODIxMDkwOVowfjELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0
// SIG // IENvZGUgU2lnbmluZyBQQ0EgMjAxMTCCAiIwDQYJKoZI
// SIG // hvcNAQEBBQADggIPADCCAgoCggIBAKvw+nIQHC6t2G6q
// SIG // ghBNNLrytlghn0IbKmvpWlCquAY4GgRJun/DDB7dN2vG
// SIG // EtgL8DjCmQawyDnVARQxQtOJDXlkh36UYCRsr55JnOlo
// SIG // XtLfm1OyCizDr9mpK656Ca/XllnKYBoF6WZ26DJSJhIv
// SIG // 56sIUM+zRLdd2MQuA3WraPPLbfM6XKEW9Ea64DhkrG5k
// SIG // NXimoGMPLdNAk/jj3gcN1Vx5pUkp5w2+oBN3vpQ97/vj
// SIG // K1oQH01WKKJ6cuASOrdJXtjt7UORg9l7snuGG9k+sYxd
// SIG // 6IlPhBryoS9Z5JA7La4zWMW3Pv4y07MDPbGyr5I4ftKd
// SIG // gCz1TlaRITUlwzluZH9TupwPrRkjhMv0ugOGjfdf8NBS
// SIG // v4yUh7zAIXQlXxgotswnKDglmDlKNs98sZKuHCOnqWbs
// SIG // YR9q4ShJnV+I4iVd0yFLPlLEtVc/JAPw0XpbL9Uj43Bd
// SIG // D1FGd7P4AOG8rAKCX9vAFbO9G9RVS+c5oQ/pI0m8GLhE
// SIG // fEXkwcNyeuBy5yTfv0aZxe/CHFfbg43sTUkwp6uO3+xb
// SIG // n6/83bBm4sGXgXvt1u1L50kppxMopqd9Z4DmimJ4X7Iv
// SIG // hNdXnFy/dygo8e1twyiPLI9AN0/B4YVEicQJTMXUpUMv
// SIG // dJX3bvh4IFgsE11glZo+TzOE2rCIF96eTvSWsLxGoGyY
// SIG // 0uDWiIwLAgMBAAGjggHtMIIB6TAQBgkrBgEEAYI3FQEE
// SIG // AwIBADAdBgNVHQ4EFgQUSG5k5VAF04KqFzc3IrVtqMp1
// SIG // ApUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYD
// SIG // VR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
// SIG // BBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0f
// SIG // BFMwUTBPoE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQu
// SIG // Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
// SIG // MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRS
// SIG // MFAwTgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9z
// SIG // b2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
// SIG // MV8yMDExXzAzXzIyLmNydDCBnwYDVR0gBIGXMIGUMIGR
// SIG // BgkrBgEEAYI3LgMwgYMwPwYIKwYBBQUHAgEWM2h0dHA6
// SIG // Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvZG9jcy9w
// SIG // cmltYXJ5Y3BzLmh0bTBABggrBgEFBQcCAjA0HjIgHQBM
// SIG // AGUAZwBhAGwAXwBwAG8AbABpAGMAeQBfAHMAdABhAHQA
// SIG // ZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEA
// SIG // Z/KGpZjgVHkaLtPYdGcimwuWEeFjkplCln3SeQyQwWVf
// SIG // Liw++MNy0W2D/r4/6ArKO79HqaPzadtjvyI1pZddZYSQ
// SIG // fYtGUFXYDJJ80hpLHPM8QotS0LD9a+M+By4pm+Y9G6XU
// SIG // tR13lDni6WTJRD14eiPzE32mkHSDjfTLJgJGKsKKELuk
// SIG // qQUMm+1o+mgulaAqPyprWEljHwlpblqYluSD9MCP80Yr
// SIG // 3vw70L01724lruWvJ+3Q3fMOr5kol5hNDj0L8giJ1h/D
// SIG // Mhji8MUtzluetEk5CsYKwsatruWy2dsViFFFWDgycSca
// SIG // f7H0J/jeLDogaZiyWYlobm+nt3TDQAUGpgEqKD6CPxNN
// SIG // ZgvAs0314Y9/HG8VfUWnduVAKmWjw11SYobDHWM2l4bf
// SIG // 2vP48hahmifhzaWX0O5dY0HjWwechz4GdwbRBrF1HxS+
// SIG // YWG18NzGGwS+30HHDiju3mUv7Jf2oVyW2ADWoUa9WfOX
// SIG // pQlLSBCZgB/QACnFsZulP0V3HjXG0qKin3p6IvpIlR+r
// SIG // +0cjgPWe+L9rt0uX4ut1eBrs6jeZeRhL/9azI2h15q/6
// SIG // /IvrC4DqaTuv/DDtBEyO3991bWORPdGdVk5Pv4BXIqF4
// SIG // ETIheu9BCrE/+6jMpF3BoYibV3FWTkhFwELJm3ZbCoBI
// SIG // a/15n8G9bW1qyVJzEw16UM0xghoKMIIaBgIBATCBlTB+
// SIG // MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
// SIG // bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
// SIG // cm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNy
// SIG // b3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExAhMzAAAE
// SIG // BGx0Bv9XKydyAAAAAAQEMA0GCWCGSAFlAwQCAQUAoIGu
// SIG // MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisG
// SIG // AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
// SIG // DQEJBDEiBCAxwKnfOzYZFcKu34F7hUfF+tja7ji1ucx7
// SIG // TobEoxaPMDBCBgorBgEEAYI3AgEMMTQwMqAUgBIATQBp
// SIG // AGMAcgBvAHMAbwBmAHShGoAYaHR0cDovL3d3dy5taWNy
// SIG // b3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBAGbhVhcD
// SIG // S+lAmDtxuXgk48DYs8Y0F0dVJKSD7xWBiYY+byvKdjIJ
// SIG // VmlH0UfEEkImWjK+9/sr+fdAvB68dSsHcHQh5Suu2gg3
// SIG // wTyahSFu97ovE8E9bxlH62z0bhEUMEFtRaoFr84GDQmP
// SIG // uZaO+VbFtHNMBgT8mXTecI6jsUZ3osrpSx5UHjCHcmXA
// SIG // QZ/rIS0iCWLjKAuHG5gXOSZ8uHO2CnvReaIDv6cg4q7h
// SIG // wiUl0jvqBc5RbiLbNcyahiEZgqwwBxsT+QeoSk/7IvSm
// SIG // I5RncY5Rg7j4yCL4HQzqZl3YHFaMEKkOYoaLAQ1beqXd
// SIG // tUdHN/WUwQn9Wb9673wBExphSDihgheUMIIXkAYKKwYB
// SIG // BAGCNwMDATGCF4Awghd8BgkqhkiG9w0BBwKgghdtMIIX
// SIG // aQIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUgYLKoZIhvcN
// SIG // AQkQAQSgggFBBIIBPTCCATkCAQEGCisGAQQBhFkKAwEw
// SIG // MTANBglghkgBZQMEAgEFAAQgU3+ko/YRhs/2SyE/XFr1
// SIG // X+qu6aJuNdWHz4SWod6PjZACBmet2ujCxRgTMjAyNTAy
// SIG // MjAxNTI4MzguNDk1WjAEgAIB9KCB0aSBzjCByzELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0
// SIG // IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNo
// SIG // aWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1EOTQ3MSUwIwYD
// SIG // VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
// SIG // oIIR6jCCByAwggUIoAMCAQICEzMAAAHpD3Ewfl3xEjYA
// SIG // AQAAAekwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
// SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
// SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
// SIG // U3RhbXAgUENBIDIwMTAwHhcNMjMxMjA2MTg0NTI2WhcN
// SIG // MjUwMzA1MTg0NTI2WjCByzELMAkGA1UEBhMCVVMxEzAR
// SIG // BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
// SIG // bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
// SIG // bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3Bl
// SIG // cmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNO
// SIG // OkE5MzUtMDNFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3Nv
// SIG // ZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG
// SIG // 9w0BAQEFAAOCAg8AMIICCgKCAgEArJqMMUEVYKeE0nN5
// SIG // 02usqwDyZ1egO2mWJ08P8sfdLtQ0h/PZ730Dc2/uX5gS
// SIG // vKaR++k5ic4x1HCJnfOOQP6b2WOTvDwgbuxqvseV3uqZ
// SIG // ULeMcFVFHECE8ZJTmdUZvXyeZ4fIJ8TsWnsxTDONbAyO
// SIG // yzKSsCCkDMFw3LWCrwskMupDtrFSwetpBfPdmcHGKYiF
// SIG // cdy09Sz3TLdSHkt+SmOTMcpUXU0uxNSaHJd9DYHAYiX6
// SIG // pzHHtOXhIqSLEzuAyJ//07T9Ucee1V37wjvDUgofXcbM
// SIG // r54NJVFWPrq6vxvEERaDpf+6DiNEX/EIPt4cmGsh7CPc
// SIG // Lbwxxp099Da+Ncc06cNiOmVmiIT8DLuQ73ZBBs1e72E9
// SIG // 7W/bU74mN6bLpdU+Q/d/PwHzS6mp1QibT+Ms9FSQUhlf
// SIG // oeumXGlCTsaW0iIyJmjixdfDTo5n9Z8A2rbAaLl1lxSu
// SIG // xOUtFS0cqE6gwsRxuJlt5qTUKKTP1NViZ47LFkJbivHm
// SIG // /jAypZPRP4TgWCrNin3kOBxu3TnCvsDDmphn8L5CHu3Z
// SIG // Mpc5vAXgFEAvC8awEMpIUh8vhWkPdwwJX0GKMGA7cxl6
// SIG // hOsDgE3ihSN9LvWJcQ08wLiwytO93J3TFeKmg93rlwOs
// SIG // VDQqM4O64oYh1GjONwJm/RBrkZdNtvsj8HJZspLLJN9G
// SIG // uEad7/UCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSRfjOJ
// SIG // xQh2I7iI9Frr/o3I7QfsTjAfBgNVHSMEGDAWgBSfpxVd
// SIG // AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQ
// SIG // hk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
// SIG // L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
// SIG // JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
// SIG // CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
// SIG // b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUt
// SIG // U3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB
// SIG // Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4G
// SIG // A1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA
// SIG // VrEqfq5rMRS3utQBPdCnp9lz4EByQ4kuEmy4b831Ywzw
// SIG // 5jnURO+bkKIWIRTHRsBym1ZiytJR1dQKc/x3ImaKMnqA
// SIG // L5B0Gh5i4cARpKMgAFcXGmlJxzSFEvS73i9ND8JnEgy4
// SIG // DdFfxcpNtEKRwxLpMCkfJH2gRF/NwMr0M5X/26AzaFih
// SIG // IKXQLC/Esws1xS5w6M8wiRqtEc8EIHhAa/BOCtsENlly
// SIG // P2ScWUv/ndxXcBuBKwRc81Ikm1dpt8bDD93KgkRQ7SdQ
// SIG // t/yZ41zAoZ5vWyww9cGie0z6ecGHb9DpffmjdLdQZjsw
// SIG // o/A5qirlMM4AivU47cOSlI2jukI3oB853V/7Wa2O/dnX
// SIG // 0QF6+XRqypKbLCB6uq61juD5S9zkvuHIi/5fKZvqDSV1
// SIG // hl2CS+R+izZyslyVRMP9RWzuPhs/lOHxRcbNkvFML6wW
// SIG // 2HHFUPTvhZY+8UwHiEybB6bQL0RKgnPv2Mc4SCpAPPEP
// SIG // EISSlA7Ws2rSR+2TnYtCwisIKkDuB/NSmRg0i5LRbzUY
// SIG // YfGQQHp59aVvuVARmM9hqYHMVVyk9QrlGHZR0fQ+ja1Y
// SIG // RqnYRk4OzoP3f/KDJTxt2I7qhcYnYiLKAMNvjISNc16y
// SIG // IuereiZCe+SevRfpZIfZsiSaTZMeNbEgdVytoyVoKu1Z
// SIG // Qbj9Qbl42d6oMpva9cL9DLUwggdxMIIFWaADAgECAhMz
// SIG // AAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUA
// SIG // MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
// SIG // Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
// SIG // TWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylN
// SIG // aWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3Jp
// SIG // dHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAx
// SIG // ODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
// SIG // YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
// SIG // VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
// SIG // BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
// SIG // MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
// SIG // 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1
// SIG // V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeF
// SIG // RiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc
// SIG // wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus
// SIG // 9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130
// SIG // /o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHI
// SIG // NSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTes
// SIG // y+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGp
// SIG // F1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+
// SIG // /NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fz
// SIG // pk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNO
// SIG // wTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLi
// SIG // Mxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5
// SIG // UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
// SIG // BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6H
// SIG // XtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG
// SIG // CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYE
// SIG // FCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSf
// SIG // pxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG
// SIG // DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRw
// SIG // Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3Mv
// SIG // UmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUH
// SIG // AwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYD
// SIG // VR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
// SIG // BBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0f
// SIG // BE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu
// SIG // Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
// SIG // XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBK
// SIG // BggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQu
// SIG // Y29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
// SIG // Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1Vffwq
// SIG // reEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1
// SIG // OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpT
// SIG // Td2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinL
// SIG // btg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l
// SIG // 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJ
// SIG // w7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2Fz
// SIG // Lixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7
// SIG // hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY
// SIG // 3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFX
// SIG // SVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFU
// SIG // a2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz
// SIG // /gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/
// SIG // AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1
// SIG // ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328
// SIG // y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
// SIG // ahC0HVUzWLOhcGbyoYIDTTCCAjUCAQEwgfmhgdGkgc4w
// SIG // gcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
// SIG // dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
// SIG // aWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
// SIG // Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNV
// SIG // BAsTHm5TaGllbGQgVFNTIEVTTjpBOTM1LTAzRTAtRDk0
// SIG // NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
// SIG // U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAq2mH9cQ5NqzJ
// SIG // 1P1SaNhhitZ8aPGggYMwgYCkfjB8MQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
// SIG // dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAOth
// SIG // k1QwIhgPMjAyNTAyMjAxMTQxMDhaGA8yMDI1MDIyMTEx
// SIG // NDEwOFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA62GT
// SIG // VAIBADAHAgEAAgIzQzAHAgEAAgITmDAKAgUA62Lk1AIB
// SIG // ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC
// SIG // oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3
// SIG // DQEBCwUAA4IBAQCB6bFkVxIaOWQjf+1aQE344RxXmwpK
// SIG // tx2Nwflw/twJfsw9sJ3BHttgW4uqIjTgusWqX3lQZIpH
// SIG // iSvn5E5MTEJYWrmE99HhG/nGmUDgZWvnu7GzM2fGB8LD
// SIG // iDhHaeo4+1ObGJlo7so2/0VWik4GEBdUTzxBPWU7Pecy
// SIG // rF1BoEc+/gTJBEgklBz6tGdXEcKIFoN409dQPc4CPshu
// SIG // CIYFn8Mbhvwgr9ovC08gktgo+LzUaBdaEerSrzgtZ/L4
// SIG // mkrFIsOXHDa263WJ8cnVmp516w7GxRvjN6nK+JFM4dOB
// SIG // knpBL00LrFFa1dx1oLITu2A5r/GqRDLUJWncgwFhYgnc
// SIG // oJ47MYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
// SIG // EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
// SIG // ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
// SIG // dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
// SIG // bXAgUENBIDIwMTACEzMAAAHpD3Ewfl3xEjYAAQAAAekw
// SIG // DQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzEN
// SIG // BgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgjz8g
// SIG // RYUVcATHltEBKEN73XPhNP9JKe/Jr+Xj++wkJzIwgfoG
// SIG // CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCkkJJ4l2k3
// SIG // Jo9UykFhfsdlOK4laKxg/E8JoFWzfarEJTCBmDCBgKR+
// SIG // MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
// SIG // dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
// SIG // aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
// SIG // Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
// SIG // 6Q9xMH5d8RI2AAEAAAHpMCIEIBoOgIpqeUYbXexrPgWQ
// SIG // YyWYxe69AuNspnMoU1Yzp90pMA0GCSqGSIb3DQEBCwUA
// SIG // BIICADfSzlqm+Te0MqMU31e9zXmXa0/l7nBknKLlyZ16
// SIG // g4DI31xN6AdDmDV9Nf16kpUWoa4dyHnAjmziBZNIsDgQ
// SIG // mLuvYiHSUNhUM7aIRv20in7v99ol4M/72NT/prZKzKJG
// SIG // 8wDx0449t3IXU823PRiCZFn3MnL/9tKfVVisNSvgoXb4
// SIG // SObhFBPG5sA8w8IUzEJ+oirhKkfpPAUAp2FZ7/qTAohv
// SIG // EPLSq4RMFhVGoJPsqz90jjZWKz8p47A4EiTrFGoKweyI
// SIG // SR39uNOA6fuyh6jAtEJG+UzrpdAnsB5fWNp0J6zMej7m
// SIG // eFIBCIs6dHyrz9qIu61O18YEdidTcoNO9E/k44YPpBnA
// SIG // +oYQcxFIOZFe71b+kAt7iqX+SdXDRCGVV1LYY5MP+Tsb
// SIG // KlqlFxnmIcjeMyUIfTaDXaIYAYEhitfUnFgLVkd4LlJT
// SIG // 7ckVgCXIGHZknjc5pnjzW9Jo9Z1kod5d1PivIK4YIbuh
// SIG // biw1vL8EVGXsmBqiccFsgh94n7jdp1HFiGrWQwGK2OzZ
// SIG // xZVFVRgpvGImwx8JwzxlHhHpvyUdBnTzdwLmdUK3tCLr
// SIG // dNnxzFg5r/piqe0kMOga1HjEjjPV017uZuHtosW9uM15
// SIG // hGtnm0cB29mX8idcgx+pGipBQNYNqvBGVIDSbM/0pFBK
// SIG // qHkzoo1puC5LYW4/FC2xnDdQ/gNy
// SIG // End signature block