O Sertão será Cloud

Managing Windows Update for Business Policies with Azure Functions and TypeScript

Windows Update for Business allows organizations to manage Windows Update policies to keep devices secure and up-to-date. This article walks you through creating an Azure Function using TypeScript to manage these policies effectively. We’ll cover setting up the function, creating and assigning policies, and adding proper logging and error handling.

Environment Setup

Before we begin, you need to set up some environment variables in the Azure Portal:

Creating a Service Principal

Adding required permissions

  • CLIENT_ID: Azure AD client ID for authentication.
  • CLIENT_SECRET: Azure AD client secret.
  • TENANT_ID: Azure AD tenant ID.

Setting Up the Azure Function

We’ll start by creating an HTTP-triggered Azure Function.

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 - The Azure Function context
* @param req - The HTTP request object
*/
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.message);
context.res = {
status: 500,
body: "Internal Server Error"
};
}
};

export default httpTrigger;

Creating the Windows Update for Business Configuration Policies Class

Next, we’ll create the WindowsUpdateForBusinessConfigurationPolicies class, which will handle the creation and assignment of policies.

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");
}
}
}

Defining the Windows Update for Business Policy Interface

Finally, we’ll define the WindowsUpdateForBusinessPolicy interface with comments to provide clear documentation.

/**
* Represents the installation schedule for Windows Update Active Hours.
*/
interface WindowsUpdateActiveHoursInstall {
/** The OData type of the installation schedule. */
"@odata.type": "#microsoft.graph.windowsUpdateActiveHoursInstall";
/** The end time of the active hours for installation. */
activeHoursEnd: string;
/** The start time of the active hours for installation. */
activeHoursStart: string;
}

/**
* Represents a Windows Update for Business Policy.
*/
export interface WindowsUpdateForBusinessPolicy {
/** The OData type of the policy. */
"@odata.type": "#microsoft.graph.windowsUpdateForBusinessConfiguration";
/** Indicates if Windows 11 upgrade is allowed. */
allowWindows11Upgrade: boolean;
/** The automatic update mode. */
automaticUpdateMode: string;
/** The auto-restart notification dismissal setting. */
autoRestartNotificationDismissal: string;
/** The business-ready updates setting. */
businessReadyUpdatesOnly: string;
/** The deadline for feature updates in days. */
deadlineForFeatureUpdatesInDays: number;
/** The deadline for quality updates in days. */
deadlineForQualityUpdatesInDays: number;
/** The grace period for deadlines in days. */
deadlineGracePeriodInDays: number;
/** The description of the policy. */
description: string;
/** The display name of the policy. */
displayName: string;
/** Indicates if drivers are excluded from updates. */
driversExcluded: boolean;
/** The engaged restart deadline in days. */
engagedRestartDeadlineInDays: number | null;
/** The engaged restart snooze schedule for feature updates in days. */
engagedRestartSnoozeScheduleForFeatureUpdatesInDays: number | null;
/** The engaged restart snooze schedule in days. */
engagedRestartSnoozeScheduleInDays: number | null;
/** The engaged restart transition schedule for feature updates in days. */
engagedRestartTransitionScheduleForFeatureUpdatesInDays: number | null;
/** The engaged restart transition schedule in days. */
engagedRestartTransitionScheduleInDays: number | null;
/** The deferral period for feature updates in days. */
featureUpdatesDeferralPeriodInDays: number;
/** Indicates if feature updates are paused. */
featureUpdatesPaused: boolean;
/** The rollback window for feature updates in days. */
featureUpdatesRollbackWindowInDays: number;
/** The installation schedule. */
installationSchedule: WindowsUpdateActiveHoursInstall;
/** Indicates if the Microsoft Update service is allowed. */
microsoftUpdateServiceAllowed: boolean;
/** Indicates if reboot is postponed until after the deadline. */
postponeRebootUntilAfterDeadline: boolean;
/** The deferral period for quality updates in days. */
qualityUpdatesDeferralPeriodInDays: number;
/** Indicates if quality updates are paused. */
qualityUpdatesPaused: boolean;
/** The role scope tag IDs. */
roleScopeTagIds: string[];
/** The schedule for imminent restart warning in minutes. */
scheduleImminentRestartWarningInMinutes: number | null;
/** The schedule for restart warning in hours. */
scheduleRestartWarningInHours: number | null;
/** Indicates if checks before restart are skipped. */
skipChecksBeforeRestart: boolean;
/** The update notification level. */
updateNotificationLevel: string;
/** The update weeks. */
updateWeeks: number | null;
/** Indicates if user pause access is enabled. */
userPauseAccess: string;
/** Indicates if user Windows Update scan access is enabled. */
userWindowsUpdateScanAccess: string;
}

Conclusion

By following this guide, you’ve set up an Azure Function to manage Windows Update for Business policies using TypeScript. This setup ensures that policies can be created and assigned programmatically, leveraging Azure AD for authentication and authorization. Proper logging and error handling have been added to improve the maintainability and reliability of the solution.

References

Stackademic 🎓

Thank you for reading until the end. Before you go:


Managing Windows Update for Business Policies with Azure Functions and TypeScript was originally published in Stackademic on Medium, where people are continuing the conversation by highlighting and responding to this story.