Skip to content

[BUG] FormatException from XmlConvert.ToTimeSpan while enumerating DPP backup jobs and policies (Azure.ResourceManager.DataProtectionBackup) #59624

Description

@shrja-ms

[BUG] FormatException from XmlConvert.ToTimeSpan while enumerating DPP backup jobs and policies

Library name and version

Azure.ResourceManager.DataProtectionBackup 1.7.1 (also reproduced on 1.7.0)

Describe the bug

Two distinct enumeration paths in the DPP (Data Protection Backup) management library throw System.FormatException from XmlConvert.ToTimeSpan when the service returns ISO 8601 duration values that the SDK's strict parser rejects (e.g. week designators like P4W, fractional seconds, or service-specific extensions). Once the exception is raised mid-pagination, the caller cannot retrieve any further items from the same listing — including items whose duration fields are valid.

Two affected code paths:

1. Listing jobs — DataProtectionBackupVaultResource.GetDataProtectionBackupJobs().GetAllAsync()

System.FormatException
   at System.Xml.XmlConvert.ToTimeSpan(String s)
   at Azure.ResourceManager.DataProtectionBackup.TypeFormatters.ParseTimeSpan(String value, String format)
   at Azure.ResourceManager.DataProtectionBackup.ModelSerializationExtensions.GetTimeSpan(JsonElement element, String format)
   at Azure.ResourceManager.DataProtectionBackup.Models.DataProtectionBackupJobProperties.DeserializeDataProtectionBackupJobProperties(JsonElement element, ModelReaderWriterOptions options)
   at Azure.ResourceManager.DataProtectionBackup.DataProtectionBackupJobData.DeserializeDataProtectionBackupJobData(JsonElement element, ModelReaderWriterOptions options)
   at Azure.ResourceManager.DataProtectionBackup.Models.AzureBackupJobResourceList.DeserializeAzureBackupJobResourceList(JsonElement element, ModelReaderWriterOptions options)
   at Azure.ResourceManager.DataProtectionBackup.Models.AzureBackupJobResourceList.FromResponse(Response response)
   at Azure.ResourceManager.DataProtectionBackup.AzureBackupJobResourcesGetAllAsyncCollectionResultOfT.AsPages(...)+MoveNext()

2. Listing / getting backup policies — DataProtectionBackupVaultResource.GetDataProtectionBackupPolicies().GetAllAsync() and .GetAsync(name)

The same parser is invoked when deserializing DataProtectionBackupAbsoluteDeleteSetting.Duration inside policy retention rules:

System.FormatException
   at System.Xml.XmlConvert.ToTimeSpan(String s)
   at Azure.ResourceManager.DataProtectionBackup.TypeFormatters.ParseTimeSpan(String value, String format)
   at Azure.ResourceManager.DataProtectionBackup.ModelSerializationExtensions.GetTimeSpan(JsonElement element, String format)
   at Azure.ResourceManager.DataProtectionBackup.Models.DataProtectionBackupAbsoluteDeleteSetting.DeserializeDataProtectionBackupAbsoluteDeleteSetting(JsonElement element, ModelReaderWriterOptions options)
   at Azure.ResourceManager.DataProtectionBackup.Models.DataProtectionBaseBackupPolicy.DeserializeDataProtectionBaseBackupPolicy(JsonElement element, ModelReaderWriterOptions options)
   at Azure.ResourceManager.DataProtectionBackup.DataProtectionBackupPolicyData.DeserializeDataProtectionBackupPolicyData(JsonElement element, ModelReaderWriterOptions options)
   at Azure.ResourceManager.DataProtectionBackup.DataProtectionBackupPolicyResource.GetAsync(CancellationToken cancellationToken)

Single-item GetAsync(policyName) fails outright; GetAllAsync() cuts the listing short.

Expected behavior

GetAllAsync() (jobs and policies) and GetAsync(name) (policies) should successfully materialize results even when the service returns duration strings the strict ISO 8601 parser doesn't accept — either by (a) using a more lenient parser, (b) round-tripping unknown values into the RawData bag instead of throwing, or (c) replacing the unparseable value with null / TimeSpan.Zero and continuing.

Actual behavior

The first malformed duration aborts deserialization and surfaces a raw FormatException to the caller. There is no error envelope, no RequestFailedException, and no way to skip just the offending item — pagination is broken at that page.

Reproduction Steps

var armClient = new ArmClient(new DefaultAzureCredential());
var vault = armClient.GetDataProtectionBackupVaultResource(
    DataProtectionBackupVaultResource.CreateResourceIdentifier(subId, rg, vaultName));

// Path 1 - jobs
await foreach (var job in vault.GetDataProtectionBackupJobs().GetAllAsync())
{
    Console.WriteLine(job.Data.Name);
}

// Path 2 - policies
await foreach (var p in vault.GetDataProtectionBackupPolicies().GetAllAsync())
{
    Console.WriteLine(p.Data.Name);
}
var single = await vault.GetDataProtectionBackupPolicies().GetAsync("policyWithLongRetention");

The exception is intermittent — it depends on whether the vault contains a job/policy whose duration field is in a format XmlConvert.ToTimeSpan rejects.

Impact

Related

Suggested fix

Either:

  1. Use a more lenient parser in TypeFormatters.ParseTimeSpan (handle week designators / fractional seconds), or
  2. Wrap XmlConvert.ToTimeSpan in a try/catch in the generated Deserialize* methods and surface unknown durations as null / preserve in RawData, or
  3. Coordinate with the Azure Backup service team to validate that all returned duration strings conform to the SDK's accepted ISO 8601 subset.

Option (2) is the safest backwards-compatible patch.

Environment

  • OS: Windows 11 (also seen on Linux containers)
  • .NET: 10.0
  • Azure.ResourceManager.DataProtectionBackup: 1.7.1
  • Azure.ResourceManager: 1.15.0

Metadata

Metadata

Assignees

Labels

ARMMgmtThis issue is related to a management-plane library.

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions