La solución a las Plantillas de Microsoft Planner que faltan

Una de las funciones más solicitadas en el foro de UserVoice para Microsoft Planner es la capacidad de crear plantillas de Microsoft Planner y hacer nuevos planes a partir de ellas.

Microsoft está trabajando en esta función, pero aún se desconoce el ETA. Recibí la misma solicitud y comencé a trabajar en un script de PowerShell que le permite duplicar planes.

Al final de este artículo, encontrará el script completo, pero primero lo guiaré a través de los pasos.

vamos a usar PowerShell y API de gráficos de Microsoft para duplicar el plan. Uso un conector personalizado para conectar con Graph.

Si no ha usado Graph con PowerShell antes, asegúrese de leer primero esta guía paso a paso de Microsoft Graph con PowerShell aquí.

Qué se requiere para duplicar un plano

Para duplicar un plan de Microsoft Planner, debemos seguir muchos pasos:

  • Obtenga el plan de plantilla y la identificación del nuevo plan
  • Copie las categorías del plan de plantilla (para las etiquetas)
  • Consigue todos los baldes
  • Obtenga toda la tarea para cada cubo
  • Agregando los nuevos cubos en el orden correcto
  • Agregar la tarea a cada cubo en el orden correcto
  • Añade las descripciones de cada tarea.
  • Agregar listas de verificación si existen

Voy a ir a través de cada paso para que pueda modificarlo a sus propias necesidades.

Creación de plantillas de planificador de Microsoft

Supongo que ya se ha conectado a Microsoft Graph. Si no, asegúrese de leer la guía paso a paso mencionada anteriormente.

Una cosa que verá mucho en los pasos a continuación es el encabezado if-match. Cada vez que se crea o actualiza un plan, depósito o tarea, se cambia el token ETag.

Entonces, antes de que podamos actualizar algo o agregar tareas a un depósito, debemos obtener el último valor de ETag.

Obtener la identificación del plan

Necesitamos el plan de plantilla y la nueva identificación del plan antes de que podamos hacer nada.

El nuevo plan se puede crear manualmente, con PowerShell o venir con un nuevo grupo de Office 365. No entraré en detalles sobre cómo puede crear el plan.

Hay dos formas de obtener una identificación de plan, la forma más simple de copiarla desde la barra de direcciones.

Simplemente abra un plan y mire la barra de direcciones. Al final de la URL encontrarás el planId:

https://tasks.office.com/~/Planner/#/plantaskboard?groupId=e54430ef-ABCD-43a6-1234-4c2f9d0987c3&planId=123ABCnzhHdE6zfvZkZIXpYAFPB_

Otra opción sería usar PowerShell para obtener el grupo unificado por nombre y buscar la ID del grupo. Asegúrese de conectarse primero a Exchange Online.

# Get Group ID from newly created Office 365 Group
$GroupId = Get-UnifiedGroup -Identity $groupName | select ExternalDirectoryObjectId

Con el GroupID podemos solicitar todos los planes disponibles para este grupo:

$url = https://graph.microsoft.com/v1.0/groups/{group-id-with-plan}/planner/plans
$plans = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $url -Method GET -ContentType 'application/json'

Copiar las categorías del plan

Tenemos las identificaciones de ambos planes, lo primero que vamos a hacer es copiar las categorías o también las etiquetas del plan de plantilla. Primero, obtenemos los detalles del plan de la plantilla:

# Get plan Details
$url = "https://graph.microsoft.com/v1.0/planner/plans/$templatePlanID/details"
$planDetails = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $url -Method 'GET' -ContentType 'application/json'

Y necesitamos el valor de eTag del nuevo plan, por lo que también solicitamos los detalles del nuevo plan:

# New plan details for eTag token
$url = "https://graph.microsoft.com/v1.0/planner/plans/$newPlanID/details"
$newPlanDetails = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $url -Method 'GET' -ContentType 'application/json'

Si miras en el $planDetails.categoryDescriptions verá un objeto JSON con todas las etiquetas. Vamos a actualizar nuestro nuevo plan con esas descripciones de categorías.

Primero, creamos los encabezados con el encabezado if-match correcto y creamos un cuerpo JSON. Luego, con un método PATCH, podemos actualizar el nuevo plan.

# Update plan details
$headers = @{
			Authorization = "Bearer $accessToken"
			ContentType="application/json"
			'if-match' = $newPlanDetails.'@odata.etag'
		}

$categories = $planDetails.categoryDescriptions | convertTo-Json

$planDetailBody = @"
	{
	  "categoryDescriptions": $($categories)
	}
"@

$url = "https://graph.microsoft.com/v1.0/planner/plans/$newPlanID/details"
Invoke-RestMethod -Headers $headers -Uri $url -Method PATCH -body $planDetailBody -ContentType 'application/json'

Si observa su nuevo plan, ahora verá que todas las etiquetas tienen la descripción correcta.

Obtenga todos los cubos y tareas

Las tareas no se enumeran en un cubo, pero son parte del plan. Por lo tanto, no solo puede obtener la tarea para un cubo, sino que tendrá que solicitar toda la tarea para el plan.

# Get all the buckets
$url = "https://graph.microsoft.com/v1.0/planner/plans/$templatePlanID/buckets"
$buckets = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $url -Method 'GET' -ContentType 'application/json'

# Get all tasks
$url = "https://graph.microsoft.com/v1.0/planner/plans/$templatePlanID/tasks"
$allTasks = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $url -Method 'GET' -ContentType 'application/json'

Creación de cubos en el nuevo plan

Los cubos y la tarea se ordenan según el valor orderHint. Este no es un número entero lógico o un valor alfabético, pero es una cadena basada en el objeto anterior y siguiente.

Si obtiene todos los cubos y comienza a agregarlos a un nuevo plan, notará que se agregan en orden inverso.

Lo mismo ocurre con las tareas. Ahora no puede simplemente usar orderHint del plan de plantilla, porque el servicio calcula orderHint.

Entonces, si agregamos un nuevo depósito o tarea, debemos verificar el orderHint que creó el servicio y usar ese valor para agregar el siguiente depósito después de él.

La documentación oficial explica bastante bien cómo funciona orderHint. Si desea agregar una tarea detrás de la tarea agregada anteriormente, solo necesita agregarle un espacio y un signo de exclamación.

Para agregar los cubos en el orden correcto, almacenamos el orderHint del último cubo creado para que podamos agregar el siguiente a la derecha.

# Create new buckets in the destination plan
$url = "https://graph.microsoft.com/v1.0/planner/buckets"

$lastBucketOrderHint=""

foreach ($bucket in $buckets.value) {	
	$body = @"
	{
	  "name": "$($bucket.name)",
	  "planId": "$newPlanID",
	  "orderHint": "$lastBucketOrderHint !"
	}
"@

	$newBucket = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $url -Method 'POST' -body $body -ContentType 'application/json'
	$lastBucketOrderHint = $newBucket.orderHint
}

Creando las tareas para cada cubo

Dentro del foreach de los cubos también vamos a agregar las tareas. Ya hemos solicitado todas las tareas del plan de plantilla, ahora solo falta obtener las tareas que pertenecen a este depósito.

Para resolver el problema del orden, podemos ordenar las tareas en orden descendente para que se agreguen en el orden correcto.

# Get the task for this bucket - Reverse order to get them in right order
$tasks = $allTasks.value | where bucketId -eq $bucket.id | Sort-Object orderHint -Descending
$createTaskUrl = "https://graph.microsoft.com/v1.0/planner/tasks"

foreach ($task in $tasks) {
	# Create the task
	$taskBody = @"
		{
		        "planId": "$newPlanId",
			"bucketId": "$($newBucket.id)",
			"title": "$($task.title)"
		}
"@

	$newTask = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $createTaskUrl -Method 'POST' -body $taskBody -ContentType 'application/json'
}

Agregar los detalles a la tarea

Los detalles de una tarea deben agregarse a través de un método de parche. Para actualizar la tarea recién creada, necesitamos el valor de eTag de la misma.

Si creamos una tarea e inmediatamente solicitamos los detalles de la misma, es posible que obtenga un error de que el objeto no existe.

Entonces, para evitar esto, agregué un pequeño retraso en el script después de que creamos una nueva tarea.

# Add delay - we need to wait until the task is created
Start-Sleep -milliseconds 500

También necesitamos la descripción de la tarea original del plan de plantilla:

# Get the task Description
$taskDetailsUrl = "https://graph.microsoft.com/v1.0/planner/tasks/$($task.id)/details"
$taskDetails = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $taskDetailsUrl -Method 'GET' -ContentType 'application/json'

Primero, obtenemos el valor de etag de la nueva tarea para que podamos crear los encabezados necesarios. Establecemos el tipo de vista previa y la descripción que obtenemos de la tarea original.

El tipo de vista previa determina lo que se muestra en el tablero del plan. Puede ser la descripción de la tarea, referencia de una lista de verificación.

# Get the new task details for the etag
$newTaskDetailsUrl = "https://graph.microsoft.com/v1.0/planner/tasks/$($newTask.id)/details"
$newDetails = Invoke-RestMethod -Headers @{Authorization = "Bearer $accessToken"} -Uri $newTaskDetailsUrl -Method 'GET' -ContentType 'application/json'

# Update task with the details
	$headers = @{
		Authorization = "Bearer $accessToken"
		ContentType="application/json"
		'if-match' = $newDetails.'@odata.etag'
	}

	# Add the task description
        if ($task.hasDescription -eq $True) {
           
		$taskUpdateDescription = @"
                {
                    "description": "$($taskDetails.description)",
                    "previewType": "$($taskDetails.previewType)" 
                }
"@
            
            $taskUpdateUrl = "https://graph.microsoft.com/v1.0/planner/tasks/$($newTask.id)/details"

            Invoke-RestMethod -Headers $headers -Uri $taskUpdateUrl -Method PATCH -body $taskUpdateDescription -ContentType 'application/json'
}

Agregar los elementos de la lista de verificación

La última parte del guión es agregar la lista de verificación. Este es el más complicado de recrear.

Primero, verificamos si la tarea original tiene una lista de verificación. Luego, si tenemos una lista de verificación, podemos comenzar a copiarla en nuestra nueva tarea.

Un elemento de la lista de verificación necesita un GUID único generado por el cliente, que podemos crear en PowerShell con el siguiente cmd:

$guid = [guid]::newGuid().guid

A continuación, creamos un objeto JSON con todos los elementos de la lista de verificación y los agregamos con un método PATCH a la tarea:

# Copy the checklist items
        if (![string]::IsNullOrEmpty($taskDetails.checklist)) {

            $checkListItems = @{}
            foreach ($item in $taskDetails.checklist.psobject.Properties) {
                $guid = [guid]::newGuid().guid

                $checkListItem = @{
                    "@odata.type" = "#microsoft.graph.plannerChecklistItem"
                    "title" = "$($item.value.title)"
                }

                $checkListItems | Add-Member -MemberType NoteProperty -Name $guid -value $checkListItem
            }

            $checkListItemsJson = $checkListItems | ConvertTo-Json

            $taskUpdateChecklist = @"
                {
                    "checklist": $($checkListItemsJson)
                }
"@

            Invoke-RestMethod -Headers $headers -Uri $taskUpdateUrl -Method PATCH -body $taskUpdateChecklist -ContentType 'application/json'
        }
	}

Terminando

Hay algunas cosas que no cubrí en esta publicación, creamos las etiquetas al principio pero no las aplicamos a las tareas recién creadas.

Si desea esto, consulte la documentación en plannerAppliedCategories. Debe actualizar una tarea recién creada para agregarlos.

Además, no copié ninguna referencia externa ni usuarios asignados a una tarea. En mi caso, esto no era necesario, pero con esta guía, tendrás un buen punto de partida para agregar esos detalles también.

El script completo para usar las plantillas de Microsoft Planner se puede descargar de mi GitHub Repo.

Si tiene alguna pregunta, siempre puede comunicarse conmigo.

También puedes contratarme para que te haga un script personalizado o te ayude con la implementación.

Gracias a Laura Kokkarinen por explicar el cubo y la clasificación de tareas.

Otros artículos relacionados

GUI de PowerShell - Cómo empezar

GUI de PowerShell – Cómo empezar

He creado muchos scripts de PowerShell durante los últimos años. Todo con un solo propósito para automatizar mi trabajo de ...
Leer Más
PowerShell HashTable - Todo lo que necesitas saber

PowerShell HashTable – Todo lo que necesitas saber

Las tablas hash en PowerShell se pueden usar para almacenar información de manera estructurada. Esto se hace mediante el uso ...
Leer Más
Cómo verificar su versión de PowerShell

Cómo verificar su versión de PowerShell

¿Está usando la versión 5.1 o ya cambió a la versión 7 de PowerShell? La versión de PowerShell que haya ...
Leer Más
Cómo usar Get-ADGroup en PowerShell

Cómo usar Get-ADGroup en PowerShell

¿Necesita obtener todos los grupos en su Active Directory o simplemente necesita encontrar la ubicación del grupo que se esconde ...
Leer Más
Guía y scripts de desinstalación, reinstalación y limpieza de Microsoft Teams

Guía y scripts de desinstalación, reinstalación y limpieza de Microsoft Teams

Microsoft Teams no es como un programa promedio cuando se trata de instalarlo y desinstalarlo. El problema con Microsoft Teams ...
Leer Más
[PowerShell] Exportar el estado de MFA de los usuarios de Office 365

[PowerShell] Exportar el estado de MFA de los usuarios de Office 365

Uno de los informes que realmente extraño en el Centro de administración de Microsoft 365 es una descripción general clara ...
Leer Más

Deja un comentario