iOfficeAI/OfficeCLI · error · ArgumentException

progId '{progId}' exceeds 39 characters (limit: 39, actual:

Error message

progId '{progId}' exceeds 39 characters (limit: 39, actual: {progId.Length}).

What it means

Thrown by OleHelper.ValidateProgId when a COM ProgID string exceeds 39 characters. The Windows COM specification (MSDN 'ProgID') limits ProgIDs to 1..39 characters. Writing a longer string into the OOXML progId attribute would produce an OLE element that Office refuses to open or activates incorrectly. This is a pre-write validation that gives the user an early, actionable error instead of a corrupted document.

Source

Thrown at src/officecli/Core/OleHelper.cs:492

    /// width/height. 2 inches × 0.75 inches matches what Office uses for a
    /// default "show as icon" OLE frame, sized to fit the file-type label.
    /// </summary>
    public const long DefaultOleWidthEmu = 1828800;  // 2 inches
    public const long DefaultOleHeightEmu = 685800;   //  0.75 inches

    /// <summary>
    /// Validate a COM ProgID string against the well-known Windows COM
    /// constraints: the identifier must be 1..39 characters long and must
    /// not start with a digit. OLE spec (MSDN "ProgID") is explicit on both
    /// rules. Handlers previously accepted arbitrary strings silently; this
    /// method gives users an early, actionable error instead of writing an
    /// invalid OLE element that Office refuses to open.
    /// </summary>
    public static void ValidateProgId(string progId)
    {
        if (progId == null) return;
        if (progId.Length > 39)
            throw new ArgumentException(
                $"progId '{progId}' exceeds 39 characters (limit: 39, actual: {progId.Length}).");
        if (progId.Length > 0 && char.IsDigit(progId[0]))
            throw new ArgumentException(
                $"progId '{progId}' cannot start with a digit.");
        // COM ProgID character set: letters, digits, '.', '_', '-'. Anything
        // else (notably XML-unsafe characters like '<', '>', '&', '"') would
        // either corrupt the OOXML progId attribute or be rejected by Office
        // on reopen. Reject early with an actionable error instead of letting
        // bad bytes land in the package.
        foreach (var ch in progId)
        {
            if (!(char.IsLetterOrDigit(ch) || ch == '.' || ch == '_' || ch == '-'))
                throw new ArgumentException(
                    $"progId '{progId}' contains invalid characters. Only letters, digits, '.', '_', '-' are allowed.");
        }
    }

    /// <summary>

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Shorten the progId to 39 characters or fewer.
  2. Use a standard Office progId like 'Word.Document.12', 'Excel.Sheet.12', 'PowerPoint.Show.12', 'Package', or 'AcroExch.Document' instead of a custom one.
  3. If you need a custom progId, abbreviate it while keeping it meaningful and within the allowed character set.

Example fix

// before — progId too long (40 chars)
add ole src=file.pdf progId=MyExtremelyLongCustomProgIdNameHere path='/body'

// after — use standard or shortened progId
add ole src=file.pdf progId=Package path='/body'
// or a shorter custom one
add ole src=file.pdf progId=MyApp.Doc path='/body'
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate progId length before embedding
if (progId != null && progId.Length > 39)
{
    Console.Error.WriteLine($"progId '{progId}' is {progId.Length} chars, max is 39.");
    progId = progId[..39]; // or use a standard progId like "Package"
}
OleHelper.ValidateProgId(progId);

Try / catch

try
{
    OleHelper.ValidateProgId(progId);
}
catch (ArgumentException ex) when (ex.Message.Contains("exceeds 39 characters"))
{
    // Shorten or switch to a standard progId like "Package"
    progId = "Package";
    OleHelper.ValidateProgId(progId);
}

Prevention

When it happens

Trigger: Calling any Add ole or Set ole operation with a 'progId' property longer than 39 characters. For example: 'add ole src=file.pdf progId=MyVeryVeryVeryVeryVeryVeryVeryVeryLongProgIdName' (40+ chars). ValidateProgId is called by ResolveProgId, which is invoked by all three handlers' AddOle/SetOle paths. A null progId passes (returns early), and an empty string passes the length check but may fail the digit-first check.

Common situations: A user who manually specifies a progId that is too long. A progId auto-generated from a filename or organization name that exceeds the limit. A copy-paste from a configuration that used a verbose identifier. An adversarial input designed to probe the validation boundary.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/d74f317f128620b4. Report an issue: GitHub.