Get Plan and Bucket ID

You can get the Plan ID from the link in the browser, it mostly starts with “rALlID_”. If you have the Plan ID, then you can get the Bucket ID with the following command:

Import-Module Microsoft.Graph.Planner 

$plannerPlanId = "rALlID_"

Get-MgPlannerPlanBucket -PlannerPlanId $plannerPlanId 

Plannner Task Creation

You can create planner tasks in powershell with the MS Graph Planner Module (Microsoft.Graph.Planner) and the custom module.

Grouping the vulnerabilities

...
$result = $response.Result  
$groupedTasks = $result | Group-Object -Property "HostName"

foreach ($vuln in $groupedTasks) {
    $TaskDescription = @{}

    $sortedTasks = $vuln.Group | Sort-Object -Property VulnerabilitySeverityLevel -Descending 

    $totalTasks = $sortedTasks.Count

    # Process in chunks of 5
    for ($i = 0; $i -lt $totalTasks; $i += 5) {
        $batch = $vuln.Group[$i..($i+4)]

        $firstVuln = $batch | Select-Object -First 1 # to get the HostName and contact for the server
        $contact = $firstVuln.contact
        $hostName = $firstVuln.HostName

Get the owner

...
$contact = if ([string]::IsNullOrWhiteSpace($contact) -or $contact -eq "NULL") { "admin@flo7000.ch" } else { $contact }
$user = Get-MgUser -Filter "Mail eq '$($contact)'"

$assignments = @{
    $user.Id = @{
        "@odata.type" = "#microsoft.graph.plannerAssignment"
        orderHint    = " !"
    }
}

Set the description

...
$TaskDescription = @{}

$taskList = $batch | ForEach-Object { 
    " 🚨 $($_.CveID) with SeverityLevel $($_.VulnerabilitySeverityLevel) and CVSS Score of $($_.CvssScore) " + "`n" +
    "Affected Software: $($_.SoftwareName) by $($_.SoftwareVendor)" + "`n" +
    ($(if (![string]::IsNullOrWhiteSpace($_.RecommendedSecurityUpdate)) { 
        "Recommended Security Update: $($_.RecommendedSecurityUpdate)" + "`n"
    } else { "" })) +
    ($(if (![string]::IsNullOrWhiteSpace($_.FirstDiskPath)) { 
        "Disk Path: $($_.FirstDiskPath)" + "`n"
    } else { "" })) +
    ($(if (![string]::IsNullOrWhiteSpace($_.FirstRegistryPath)) { 
        "Registry Path: $($_.FirstRegistryPath)" + "`n"
    } else { "" }))
}

$formattedTasks = $taskList -join "`n"

$TextBody = @"
Serverowner: $($user.UserPrincipalName)

$formattedTasks

For more Informations:
- Process: Help-Artikel
- Vulnerability: NIST Database
"@

$TaskDescription.Clear()
$TaskDescription.Add('description', $TextBody)

There isn’t a description of the vulnerability added because the links are added as reference - just to avoid the planner task description length limit (32k characters).

Set the priority and End Date

...
$priorityOrder = @("Critical", "High", "Medium", "Low")
$highestPriorityTask = $batch | Sort-Object { $priorityOrder.IndexOf($_.VulnerabilitySeverityLevel) } | Select-Object -First 1
$highestPrio = $highestPriorityTask.VulnerabilitySeverityLevel # Gets the prio in text
$allCVEs = $batch.CveId

switch ($highestPrio) { # Gets the prio in a number
    "Critical" { $taskPrio = "1"; $daysToSolve = "+7"; break }
    "High"     { $taskPrio = "3"; $daysToSolve = "+10"; break }
    "Medium"   { $taskPrio = "6"; $daysToSolve = "+14"; break }
    "Low"      { $taskPrio = "9"; $daysToSolve = "+21"; break }
    default    { $taskPrio = "10"; $daysToSolve = "+100" }
}

Create the task

$allCVEs = $batch.CveId # Get all CVEs for the references

$planId = "rALlID_"
$bucketId = "your-bucket-id"
$helpArtikelLink = "https%3A//docs%2Eflows%2Eli/svn"
$helpArtikelAlias = "Documentation :)"

New-VulnerabilityNotification -GraphToken $graphToken -PlanID $planId -BucketID $bucketId -AllCVEs $allCVEs -hostName $hostName -TaskDescription $TaskDescription  -taskAssignment $assignments -severityLevel $highestPrio -taskPrio $taskPrio -daysToSolve $daysToSolve -helpArtikelLink $helpArtikelLink -helpArtikelAlias $helpArtikelAlias

Manage the task

To get the closed tasks, add them to the custom table and delete them after X days you can use taskhousekeeping.ps1