msgraph-sdk-powershell
                                
                                 msgraph-sdk-powershell copied to clipboard
                                
                                    msgraph-sdk-powershell copied to clipboard
                            
                            
                            
                        New-MgDeviceManagementDeviceConfiguration nested dictionaries
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.
@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.
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
    }
}
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.
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"
}
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.
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.
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