microsoftgraph / msgraph-sdk-powershell

Powershell SDK for Microsoft Graph
https://www.powershellgallery.com/packages/Microsoft.Graph
Other
704 stars 168 forks source link

New-MgDeviceManagementDeviceConfiguration nested dictionaries #1319

Closed jspern closed 1 year ago

jspern commented 2 years ago

Similar to this open issue for the Azure module, when creating device configuration profiles of certain types (perhaps all of them, although I only verified for one), there are nested properties that will not be set on the resulting configuration profile if they are in hashtable format; it will only work if they are System.Collections.Generic.Dictionary[System.String, System.Object]. I have confirmed this while trying to create #microsoft.graph.windows10EndpointProtectionConfiguration type configuration profiles.

In my example, AddtionalProperties.bitLockerSystemDrivePolicy and AdditionalProperties.bitLockerFixedDrivePolicy are two such properties that I am forced to use dictionaries for.

peombwa commented 2 years ago

@jspern, please share a complete repro of how you are creating windows10EndpointProtectionConfiguration and calling New-MgDeviceManagementDeviceConfiguration.

Please note that we use the same code generator as the Az module.

jspern commented 2 years ago

Here is a representation of expected formatting using a hashtable:

$NewConfigProfile = @{
  DisplayName          = 'DC001-Win10: Require BitLocker encryption on all portable devices-v1.0'
  AdditionalProperties = @{
    '@odata.type'              = '#microsoft.graph.windows10EndpointProtectionConfiguration'
    bitLockerEncryptDevice     = $true
    bitLockerSystemDrivePolicy = @{
        startupAuthenticationRequired          = $true
        startupAuthenticationTpmUsage          = 'allowed'
        startupAuthenticationTpmPinUsage       = 'allowed'
        startupAuthenticationTpmKeyUsage       = 'allowed'
        startupAuthenticationTpmPinAndKeyUsage = 'allowed'
      recoveryOptions                        = @{
          recoveryPasswordUsage                          = 'allowed'
          recoveryKeyUsage                               = 'allowed'
          hideRecoveryOptions                            = $true
          enableRecoveryInformationSaveToStore           = $true
          recoveryInformationToStore                     = 'passwordAndKey'
          enableBitLockerAfterRecoveryInformationToStore = $true
      }
    }
    bitLockerFixedDrivePolicy  = @{
      requireEncryptionForWriteAccess = $true
      recoveryOptions                 = @{
          recoveryPasswordUsage                          = 'allowed'
          recoveryKeyUsage                               = 'allowed'
          hideRecoveryOptions                            = $true
          enableRecoveryInformationSaveToStore           = $true
          recoveryInformationToStore                     = 'passwordAndKey'
          enableBitLockerAfterRecoveryInformationToStore = $true
      }
    }
  }
}

Which is sent to the API endpoint like so: New-MgDeviceManagementDeviceConfiguration @NewConfigProfile

The hashtables nested under AdditionalProperties are discarded by the endpoint, unless they are of type System.Collections.Generic.Dictionary[System.String, System.Object], which results in the following code having to be used to create the object being sent to the endpoint:

$BitLockerSystemDrivePolicy = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new()
$BitLockerSystemDrivePolicy.Add('startupAuthenticationRequired', $true)
$BitLockerSystemDrivePolicy.Add('startupAuthenticationBlockWithoutTpmChip', $false)
$BitLockerSystemDrivePolicy.Add('startupAuthenticationTpmUsage', 'allowed')
$BitLockerSystemDrivePolicy.Add('startupAuthenticationTpmPinUsage', 'allowed')
$BitLockerSystemDrivePolicy.Add('startupAuthenticationTpmKeyUsage', 'allowed')
$BitLockerSystemDrivePolicy.Add('startupAuthenticationTpmPinAndKeyUsage', 'allowed')
$BitLockerSystemDrivePolicy.Add('prebootRecoveryEnableMessageAndUrl', $false)
$BitLockerSystemDrivePolicyRecoveryOptions = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new()
$BitLockerSystemDrivePolicyRecoveryOptions.Add('blockDataRecoveryAgent', $false)
$BitLockerSystemDrivePolicyRecoveryOptions.Add('recoveryPasswordUsage', 'allowed')
$BitLockerSystemDrivePolicyRecoveryOptions.Add('recoveryKeyUsage', 'allowed')
$BitLockerSystemDrivePolicyRecoveryOptions.Add('hideRecoveryOptions', $true)
$BitLockerSystemDrivePolicyRecoveryOptions.Add('enableRecoveryInformationSaveToStore', $true)
$BitLockerSystemDrivePolicyRecoveryOptions.Add('recoveryInformationToStore', 'passwordAndKey')
$BitLockerSystemDrivePolicyRecoveryOptions.Add('enableBitLockerAfterRecoveryInformationToStore', $true)
$BitLockerSystemDrivePolicy.Add('recoveryOptions', $BitLockerSystemDrivePolicyRecoveryOptions)
$BitLockerFixedDrivePolicy = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new()
$BitLockerFixedDrivePolicy.Add('requireEncryptionForWriteAccess', $true)
$BitLockerFixedDrivePolicyRecoveryOptions = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new()
$BitLockerFixedDrivePolicyRecoveryOptions.Add('blockDataRecoveryAgent', $false)
$BitLockerFixedDrivePolicyRecoveryOptions.Add('recoveryPasswordUsage', 'allowed')
$BitLockerFixedDrivePolicyRecoveryOptions.Add('recoveryKeyUsage', 'allowed')
$BitLockerFixedDrivePolicyRecoveryOptions.Add('hideRecoveryOptions', $true)
$BitLockerFixedDrivePolicyRecoveryOptions.Add('enableRecoveryInformationSaveToStore', $true)
$BitLockerFixedDrivePolicyRecoveryOptions.Add('recoveryInformationToStore', 'passwordAndKey')
$BitLockerFixedDrivePolicyRecoveryOptions.Add('enableBitLockerAfterRecoveryInformationToStore', $true)
$BitLockerFixedDrivePolicy.Add('recoveryOptions', $BitLockerFixedDrivePolicyRecoveryOptions)
$ConfigProfile = @{
    DisplayName          = 'DC001-Win10: Require BitLocker encryption on all portable devices-v1.0'
    AdditionalProperties = @{
      '@odata.type'              = '#microsoft.graph.windows10EndpointProtectionConfiguration'
      bitLockerEncryptDevice     = $true
      bitLockerSystemDrivePolicy = $BitLockerSystemDrivePolicy
      bitLockerFixedDrivePolicy  = $BitLockerFixedDrivePolicy
    }
}
peombwa commented 2 years ago

Thanks for the repro steps!

This is due to the same root cause as https://github.com/Azure/azure-powershell/issues/12267 since we share the same code generator. A fix will need to come for the shared code generator, Azure's AutoREST.PowerShell.

I'll open an issue against AutoREST.PowerShell repo to track this serialization bug.

peombwa commented 1 year ago

The issue is no longer reproducible in v2.x (tested the repro steps above with v2.1.0). Here is the serialized output of $NewConfigProfile that's sent to the service:

➜ New-MgDeviceManagementDeviceConfiguration @NewConfigProfile -Debug

Confirm
Are you sure you want to perform this action?
Performing the operation "New-MgDeviceManagementDeviceConfiguration_CreateExpanded" on target "Call remote 'POST
/deviceManagement/deviceConfigurations' operation".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): A
DEBUG: ============================ HTTP REQUEST ============================

HTTP Method:
POST

Absolute Uri:
https://graph.microsoft.com/v1.0/deviceManagement/deviceConfigurations

Headers:
FeatureFlag                   : 00000043
Cache-Control                 : no-store, no-cache
User-Agent                    : Mozilla/5.0,(Windows NT 10.0; Microsoft Windows 10.0.22621; en-US),PowerShell/7.3.6
Accept-Encoding               : gzip
SdkVersion                    : graph-powershell/2.1.0
client-request-id             : 37dd01be-ac13-4ca3-8e5f-40f1ea18d4d1

Body:
{
  "@odata.type": "#microsoft.graph.windows10EndpointProtectionConfiguration",
  "bitLockerSystemDrivePolicy": {
    "startupAuthenticationTpmPinUsage": "allowed",
    "startupAuthenticationTpmPinAndKeyUsage": "allowed",
    "startupAuthenticationTpmKeyUsage": "allowed",
    "recoveryOptions": {
      "recoveryInformationToStore": "passwordAndKey",
      "enableRecoveryInformationSaveToStore": true,
      "recoveryPasswordUsage": "allowed",
      "enableBitLockerAfterRecoveryInformationToStore": true,
      "recoveryKeyUsage": "allowed",
      "hideRecoveryOptions": true
    },
    "startupAuthenticationRequired": true,
    "startupAuthenticationTpmUsage": "allowed"
  },
  "bitLockerFixedDrivePolicy": {
    "requireEncryptionForWriteAccess": true,
    "recoveryOptions": {
      "recoveryInformationToStore": "passwordAndKey",
      "enableRecoveryInformationSaveToStore": true,
      "recoveryPasswordUsage": "allowed",
      "enableBitLockerAfterRecoveryInformationToStore": true,
      "recoveryKeyUsage": "allowed",
      "hideRecoveryOptions": true
    }
  },
  "bitLockerEncryptDevice": true,
  "displayName": "DC001-Win10: Require BitLocker encryption on all portable devices-v1.0"
}
ghost commented 1 year ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.

jspern commented 1 year ago

I'm sorry to say that I am still able to reproduce this issue, even with the current version of 2.2.0. The bitLockerSystemDrivePolicy and bitLockerFixedDrivePolicy objects are being completely discarded upon submission, by which I mean the only setting that is applied to the resulting profile is the bitLockerEncryptDevice setting.

jspern commented 1 year ago

I did some more testing and it appears that both of the methods I mentioned in the original post are now not working. This is using the -DisplayName and -AdditionalProperties parameters. If I instead use the -BodyParameter parameter with the object constructed like below, it does work, but only with the Beta module:

$ConfigProfile = @{
    DisplayName          = 'DC001-Win10: Require BitLocker encryption on all portable devices-v1.0'
      '@odata.type'              = '#microsoft.graph.windows10EndpointProtectionConfiguration'
      bitLockerEncryptDevice     = $true
      bitLockerSystemDrivePolicy = @{
        '@odata.type'                            = '#microsoft.graph.bitLockerSystemDrivePolicy'
        startupAuthenticationRequired            = $true
        startupAuthenticationBlockWithoutTpmChip = $false
        startupAuthenticationTpmUsage            = 'allowed'
        startupAuthenticationTpmPinUsage         = 'allowed'
        startupAuthenticationTpmKeyUsage         = 'allowed'
        prebootRecoveryEnableMessageAndUrl       = $false
        recoveryOptions                          = @{
          '@odata.type'                                  = '#microsoft.graph.bitLockerRecoveryOptions'
          blockDataRecoveryAgent                         = $false
          recoveryPasswordUsage                          = 'allowed'
          recoveryKeyUsage                               = 'allowed'
          hideRecoveryOptions                            = $true
          enableRecoveryInformationSaveToStore           = $true
          recoveryInformationToStore                     = 'passwordAndKey'
          enableBitLockerAfterRecoveryInformationToStore = $true
        }
      }
  }

New-MgBetaDeviceManagementDeviceConfiguration -BodyParameter $ConfigProfile

image