O Sertão será Cloud

Gerenciando Políticas do Windows Update for Business com Azure Functions e TypeScript

O Windows Update for Business permite que as organizações gerenciem políticas de atualização do Windows para manter os dispositivos seguros e atualizados. Este artigo orienta você na criação de uma Azure Function usando TypeScript para gerenciar essas políticas de forma eficaz. Vamos cobrir a configuração da função, criação e atribuição de políticas, e a adição de logs e tratamento de erros adequados.

Configuração do Ambiente

Antes de começarmos, é necessário configurar algumas variáveis de ambiente no Azure Portal:

Criando um Service Principal

Adicionando as permissões necessárias

  • CLIENT_ID: ID do cliente Azure AD para autenticação.
  • CLIENT_SECRET: Segredo do cliente Azure AD.
  • TENANT_ID: ID do locatário Azure AD.

Configurando a Azure Function

Começaremos criando uma Azure Function acionada por HTTP.

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { ServicePrincipal } from "../src/service/ServicePrincipal";
import { WindowsUpdateForBusinessConfigurationPolicies } from "../src/service/WindowsUpdateForBusinessConfigurationPolicies";

/**
* HTTP trigger Azure Function to manage Windows Update for Business policies.
* @param {Context} context - The context object for the Azure Function.
* @param {HttpRequest} req - The HTTP request object.
* @returns {Promise<void>} - A promise that resolves when the function completes.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
context.log('HTTP trigger function processed a request.');

try {
// Validate request method
if (req.method !== "POST") {
context.res = {
status: 405,
body: "Method Not Allowed",
};
return;
}

// Extract environment variables
const clientId: string = String(process.env.CLIENT_ID);
const clientSecret: string = String(process.env.CLIENT_SECRET);
const tenantId: string = String(process.env.TENANT_ID);

// Initialize ServicePrincipal with Azure AD credentials
context.log('Initializing ServicePrincipal with tenantId:', tenantId);
const servicePrincipal = new ServicePrincipal(tenantId, clientId, clientSecret);

// Request access token from Azure AD
context.log('Requesting access token');
const accessToken: string = await servicePrincipal.getAccessToken();
context.log('Access token received');

const windowsUpdateForBusinessPolicies = new WindowsUpdateForBusinessConfigurationPolicies(
this.name,
this.description
);

let policyId: string;

// Determine policyId
if (!req.body.policyId) {
context.log('Creating new Windows Update policy');
const windowsUpdatePolicy = await windowsUpdateForBusinessPolicies.postUpdateRingPolicy(accessToken);
policyId = windowsUpdatePolicy.body.id;
context.log('Policy created with ID:', policyId);
} else {
policyId = req.body.policyId;
context.log('Using existing policy ID:', policyId);
}

// Assign the policy to the specified group
context.log('Assigning policy to group with ID:', req.body.groupId);
const response = await windowsUpdateForBusinessPolicies.assignPolicy(
policyId,
req.body.groupId,
accessToken
);
context.log('Policy assignment response:', response);

// Prepare and send the HTTP response
context.res = {
status: 201,
body: response.body
};
context.log('Response sent');
} catch (error) {
context.log.error('Error processing request:', error.message);
context.res = {
status: 500,
body: "Internal Server Error"
};
}
};

export default httpTrigger;

Criando a Classe de Políticas de Configuração do Windows Update for Business

Em seguida, criaremos a classe WindowsUpdateForBusinessConfigurationPolicies, que gerenciará a criação e atribuição de políticas.

import axios, { AxiosRequestConfig } from "axios";
import { WindowsUpdateForBusinessPolicy } from "../interfaces/WindowsUpdateForBusinessPolicy";

/**
* Interface representing the output structure of Windows Update for Business Policy.
*/
interface OutputWindowsUpdateForBusinessPolicy extends WindowsUpdateForBusinessPolicy {
id: string;
createdDateTime: Date;
lastModifiedDateTime: Date;
}

/**
* Class to manage Windows Update for Business Configuration Policies.
*/
export class WindowsUpdateForBusinessConfigurationPolicies {
private graphBaseUrl: string = "https://graph.microsoft.com/beta";

/**
* Constructor to initialize the policy name and description.
* @param name - The name of the policy.
* @param description - The description of the policy.
*/
constructor(private readonly name: string, private readonly description: string) {}

/**
* Method to post a new Windows Update Ring Policy.
* @param accessToken - The access token for Azure AD.
* @returns A promise with the status and body of the created policy.
*/
async postUpdateRingPolicy(accessToken: string): Promise<{ status: number; body: OutputWindowsUpdateForBusinessPolicy; }> {
try {
const url = `${this.graphBaseUrl}/deviceManagement/deviceConfigurations`;
const windowsUpdateConfig: WindowsUpdateForBusinessPolicy = {
"@odata.type": "#microsoft.graph.windowsUpdateForBusinessConfiguration",
allowWindows11Upgrade: true,
automaticUpdateMode: "autoInstallAtMaintenanceTime",
autoRestartNotificationDismissal: "notConfigured",
businessReadyUpdatesOnly: "userDefined",
deadlineForFeatureUpdatesInDays: 5,
deadlineForQualityUpdatesInDays: 5,
deadlineGracePeriodInDays: 3,
description: this.description,
displayName: this.name,
driversExcluded: false,
engagedRestartDeadlineInDays: null,
engagedRestartSnoozeScheduleForFeatureUpdatesInDays: null,
engagedRestartSnoozeScheduleInDays: null,
engagedRestartTransitionScheduleForFeatureUpdatesInDays: null,
engagedRestartTransitionScheduleInDays: null,
featureUpdatesDeferralPeriodInDays: 0,
featureUpdatesPaused: false,
featureUpdatesRollbackWindowInDays: 10,
installationSchedule: {
"@odata.type": "#microsoft.graph.windowsUpdateActiveHoursInstall",
activeHoursEnd: "17:00:00.0000000",
activeHoursStart: "08:00:00.0000000"
},
microsoftUpdateServiceAllowed: true,
postponeRebootUntilAfterDeadline: false,
qualityUpdatesDeferralPeriodInDays: 10,
qualityUpdatesPaused: false,
roleScopeTagIds: [],
scheduleImminentRestartWarningInMinutes: null,
scheduleRestartWarningInHours: null,
skipChecksBeforeRestart: false,
updateNotificationLevel: "restartWarningsOnly",
updateWeeks: null,
userPauseAccess: "enabled",
userWindowsUpdateScanAccess: "enabled"
};

const config: AxiosRequestConfig = {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
};

const response = await axios.post<OutputWindowsUpdateForBusinessPolicy>(url, windowsUpdateConfig, config);

return {
status: 201,
body: response.data,
};
} catch (error) {
console.error("Error creating windows update policy:", error);
throw new Error("Failed to create windows update policy");
}
}

/**
* Method to assign a policy to a specific group.
* @param policyId - The ID of the policy to be assigned.
* @param groupId - The ID of the group to assign the policy to.
* @param accessToken - The access token for Azure AD.
* @returns {Promise<{status: number, body: { ok: boolean }}>} - A promise that resolves to the response of the assignment operation.
* @throws {Error} - Throws an error if the HTTP request fails.
*/
public async assignPolicy(policyId: string, groupId: string, accessToken: string): Promise<{ status: number; body: { ok: boolean }; }> {
try {
const url = `${this.graphBaseUrl}/deviceManagement/deviceCompliancePolicies/${policyId}/assign`;

const assignment = {
assignments: [
{
target: {
"@odata.type": "#microsoft.graph.groupAssignmentTarget",
groupId: groupId,
},
},
],
};

const config: AxiosRequestConfig = {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
};

await axios.post(url, assignment, config);

return {
status: 204,
body: { ok: true },
};
} catch (error) {
console.error("Error assigning windows update policy:", error);
throw new Error("Failed to assign windows update policy");
}
}
}

Definindo a Interface da Política do Windows Update for Business

Por fim, definiremos a interface WindowsUpdateForBusinessPolicy com comentários para fornecer uma documentação clara.

/**
* Representa o cronograma de instalação para Horas Ativas do Windows Update.
*/
interface WindowsUpdateActiveHoursInstall {
/** O tipo OData do cronograma de instalação. */
"@odata.type": "#microsoft.graph.windowsUpdateActiveHoursInstall";
/** A hora de término das horas ativas para instalação. */
activeHoursEnd: string;
/** A hora de início das horas ativas para instalação. */
activeHoursStart: string;
}

/**
* Representa uma política do Windows Update for Business.
*/
export interface WindowsUpdateForBusinessPolicy {
/** O tipo OData da política. */
"@odata.type": "#microsoft.graph.windowsUpdateForBusinessConfiguration";
/** Indica se a atualização para o Windows 11 é permitida. */
allowWindows11Upgrade: boolean;
/** O modo de atualização automática. */
automaticUpdateMode: string;
/** O nível de notificação de reinicialização automática. */
autoRestartNotificationDismissal: string;
/** O nível de atualizações prontas para negócios. */
businessReadyUpdatesOnly: string;
/** O prazo para atualizações de recursos em dias. */
deadlineForFeatureUpdatesInDays: number;
/** O prazo para atualizações de qualidade em dias. */
deadlineForQualityUpdatesInDays: number;
/** O período de graça para prazos em dias. */
deadlineGracePeriodInDays: number;
/** A descrição da política. */
description: string;
/** O nome da política. */
displayName: string;
/** Indica se drivers são excluídos das atualizações. */
driversExcluded: boolean;
/** O prazo para reinicialização engajada em dias. */
engagedRestartDeadlineInDays: number | null;
/** O cronograma de adiamento de reinicialização engajada para atualizações de recursos em dias. */
engagedRestartSnoozeScheduleForFeatureUpdatesInDays: number | null;
/** O cronograma de adiamento de reinicialização engajada em dias. */
engagedRestartSnoozeScheduleInDays: number | null;
/** O cronograma de transição de reinicialização engajada para atualizações de recursos em dias. */
engagedRestartTransitionScheduleForFeatureUpdatesInDays: number | null;
/** O cronograma de transição de reinicialização engajada em dias. */
engagedRestartTransitionScheduleInDays: number | null;
/** O período de adiamento para atualizações de recursos em dias. */
featureUpdatesDeferralPeriodInDays: number;
/** Indica se atualizações de recursos estão pausadas. */
featureUpdatesPaused: boolean;
/** A janela de rollback para atualizações de recursos em dias. */
featureUpdatesRollbackWindowInDays: number;
/** O cronograma de instalação. */
installationSchedule: WindowsUpdateActiveHoursInstall;
/** Indica se o serviço Microsoft Update é permitido. */
microsoftUpdateServiceAllowed: boolean;
/** Indica se a reinicialização é adiada até após o prazo. */
postponeRebootUntilAfterDeadline: boolean;
/** O período de adiamento para atualizações de qualidade em dias. */
qualityUpdatesDeferralPeriodInDays: number;
/** Indica se atualizações de qualidade estão pausadas. */
qualityUpdatesPaused: boolean;
/** Os IDs das tags de escopo de função. */
roleScopeTagIds: string[];
/** O cronograma de aviso de reinicialização iminente em minutos. */
scheduleImminentRestartWarningInMinutes: number | null;
/** O cronograma de aviso de reinicialização em horas. */
scheduleRestartWarningInHours: number | null;
/** Indica se verificações antes da reinicialização são ignoradas. */
skipChecksBeforeRestart: boolean;
/** O nível de notificação de atualização. */
updateNotificationLevel: string;
/** As semanas de atualização. */
updateWeeks: number | null;
/** Indica se o acesso de pausa do usuário está habilitado. */
userPauseAccess: string;
/** Indica se o acesso de verificação de atualização do Windows pelo usuário está habilitado. */
userWindowsUpdateScanAccess: string;
}

Conclusão

Seguindo este guia, você configurou uma Azure Function para gerenciar políticas do Windows Update for Business usando TypeScript. Esta configuração garante que as políticas possam ser criadas e atribuídas programaticamente, aproveitando o Azure Entra ID para autenticação e autorização. A adição de logs e tratamento de erros melhora a mantenabilidade e confiabilidade da solução.

Referencias