Search code examples
exceloffice-automationoffice-scripts

ExcelScript fails on webapp, succeeds on desktop


Last year I wrote a simple script to keep a locked sheet protected but allow users to insert new rows into a table when they press the designated button. It always succeeded with few errors, normally these would be caused if the API call failed or if the user provided incorrect parameters. Now it will consistently say "ERROR Line ##: Worksheet getProtection: Write Operation is not supported for Office when a modal dialog is open." and it only does so for the webapp/browser environment, with the desktop app never running into the issue.

Do I need to redo how I declare the variables in the function or is there some method I missed that is required now? I don't understand why it would disallow writing to the file just because a dialog box it calls is asking for parameters.

I tried modifying my code to use different worksheetProtection methods, namely pauseProtection and resumeProtection, and it didn't work. I also tried nesting the unprotect/pauseProtection in other methods with no improvement.

Here is my original code that I confirmed still runs fine on desktop minutes before this post was made:

function main(workbook: ExcelScript.Workbook, starting_row: number, quantity_newRows: number) {
    let selectedSheet = workbook.getWorksheet("Schedule");
    console.log(quantity_newRows + starting_row);
    // Unprotect sheet Schedule
    selectedSheet.getProtection().unprotect("1234");
    let schedule = workbook.getTable("Schedule");
    let rowAdd = starting_row - workbook.getTable("Schedule").getHeaderRowRange().getRowIndex() - 2;

    // Insert row at index 10 into table schedule
    for (let counter = 0; counter < quantity_newRows; counter++) {
        schedule.addRow(rowAdd);
    }

    selectedSheet.getRange().getFormat().autofitRows();
    selectedSheet.getProtection().protect({ allowAutoFilter: true, allowDeleteColumns: false, allowDeleteRows: false,
        allowEditObjects: false, allowEditScenarios: false, allowFormatCells: false,
        allowFormatColumns: true, allowFormatRows: true, allowInsertColumns: false,
        allowInsertHyperlinks: false, allowInsertRows: true, allowPivotTables: false,
        allowSort: false, selectionMode: ExcelScript.ProtectionSelectionMode.normal }, "1234");
}

Solution

  • It looks like the online version of Excel tries to get ahead of itself and tries to execute the line of code with unprotect, but it is too slow and hasn't closed the dialog box yet. Pausing the script for a second or two solves the problem:

        function main(workbook: ExcelScript.Workbook, starting_row: number, quantity: number) {
            pause(1);
            insertRows(workbook, starting_row, quantity);
        }
        
        function insertRows(workbook: ExcelScript.Workbook, starting_row: number, quantity_newRows: number) {
            let selectedSheet = workbook.getWorksheet("Schedule");
            // Unprotect sheet Schedule
            selectedSheet.getProtection().unprotect("1234");
            let schedule = workbook.getTable("Schedule");
            let rowAdd = starting_row - workbook.getTable("Schedule").getHeaderRowRange().getRowIndex() - 2;
        
            // Insert row at index 10 into table schedule
            for (let counter = 0; counter < quantity_newRows; counter++) {
                schedule.addRow(rowAdd);
            }
        
            selectedSheet.getRange().getFormat().autofitRows();
            selectedSheet.getProtection().protect({
                allowAutoFilter: true, allowDeleteColumns: false, allowDeleteRows: false,
                allowEditObjects: false, allowEditScenarios: false, allowFormatCells: false,
                allowFormatColumns: true, allowFormatRows: true, allowInsertColumns: false,
                allowInsertHyperlinks: false, allowInsertRows: true, allowPivotTables: false,
                allowSort: false, selectionMode: ExcelScript.ProtectionSelectionMode.normal
            }, "1234");
        }
        
        function pause(seconds: number) {
            const waitUntil = new Date().getTime() + seconds * 1000;
            while (new Date().getTime() < waitUntil) {
            }
        }