I used what I found Here and it worked great for a few months. My Workbook would always have the latest table update. Starting today it fails every time. Referencing Script 2 during the Flow-
Worksheet getRange: The request failed with status code of 504, error code UnknownError"
//resizes the range
let rang: ExcelScript.Range = SelectedSheet.getRange("A2").getResizedRange(valuesRowCount, valuesColumnCount)
Script 1 =
function main(workbook: ExcelScript.Workbook)
{
let selectedSheet = workbook.getActiveWorksheet();
let usedRange = selectedSheet.getUsedRange();
// Delete range B:D
selectedSheet.getRange("B:D").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range G:I
selectedSheet.getRange("G:I").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range L:L
selectedSheet.getRange("L:L").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range M:M
selectedSheet.getRange("M:M").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range N:S
selectedSheet.getRange("N:S").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range P:X
selectedSheet.getRange("P:X").delete(ExcelScript.DeleteShiftDirection.left);
// Delete range R:AW
selectedSheet.getRange("R:AW").delete(ExcelScript.DeleteShiftDirection.left);
let newTable = selectedSheet.addTable(usedRange, true);
//get table
let tbl: ExcelScript.Table = selectedSheet.getTable("Table1");
//get table's column count
let tblColumnCount: number = tbl.getColumns().length;
//set number of columns to keep
let columnsToKeep: number = 22;
//set the number of rows to remove
let rowsToRemove: number = 0;
//resize the table range
let tblRange: ExcelScript.Range =
tbl.getRangeBetweenHeaderAndTotal().getResizedRange(rowsToRemove,
columnsToKeep - tblColumnCount);
//get the table values
let tblRangeValues: string[][] = tblRange.getValues() as string[][];
//create a JSON string
let result: string = JSON.stringify(tblRangeValues);
//return JSON string
return result;
Script 2 =
function main(workbook: ExcelScript.Workbook, tableValues: string) {
let selectedSheet = workbook.getActiveWorksheet();
let SelectedSheet: ExcelScript.Worksheet = workbook.getWorksheet("Database")
let usedRange = selectedSheet.getUsedRange();
//parses the JSON string to create array
let tableValuesArray: string[][] = JSON.parse(tableValues);
//gets row count from the array
let valuesRowCount: number = tableValuesArray.length - 1
//gets column count from the array
let valuesColumnCount: number = tableValuesArray[0].length - 1
//resizes the range
let rang: ExcelScript.Range = SelectedSheet.getRange("A2").getResizedRange(valuesRowCount, valuesColumnCount)
//sets the value of the resized range to the array
rang.setValues(tableValuesArray)
// Fit the width of all the columns in the Table.
SelectedSheet.getUsedRange().getFormat().autofitColumns();
selectedSheet.getUsedRange().getFormat().setHorizontalAlignment(ExcelScript.HorizontalAlignment.left);
}
Any idea what I am doing wrong?
Are you running Script 1 as part of a Flow too? If so, you should change this line in Script 1:
let selectedSheet = workbook.getActiveWorksheet();
When you run a script as part of a Flow, you need to specify the worksheet by ID or name. So, for example, you would change the line to something like:
let selectedSheet = workbook.getWorksheet("Sheet1")
where Sheet1 is the sheet you want to add the table to.
You have two different sheet variables in Script 2 as well. One is 'selectedSheet' and the other is "SelectedSheet'. You reference both later in the script. Do you mean to have both? And again, for scripts running as part of a Flow, always specify the sheet by ID or name that you are trying to reference. Let me know if that fixes things!
Have you tried rerunning your flow? It's possible there could have been some temporary issue with PowerAutomate. So perhaps the service was down.
The only potential issue I see is this
let newTable = selectedSheet.addTable(usedRange, true);
//get table
let tbl: ExcelScript.Table = selectedSheet.getTable("Table1");
The newTable's name is not guaranteed to be Table1. So if it wasn't, your flow might not work correctly. You could get around that by setting tbl to the newTable. So you could either write code like this:
let newTable = selectedSheet.addTable(usedRange, true);
//get table
let tbl: ExcelScript.Table = newTable;
Or just update tbl to code like this:
let tbl: ExcelScript.Table = selectedSheet.addTable(usedRange, true);
Related
I have a script that will move a row to another table if a certain cell within that row is filled.
The script then deletes the entire row. I would like for this to continue but instead of deleting the entire row it would be great if it deleted the entire row excepting the first 2 columns.
The current script is below:
function main(workbook: ExcelScript.Workbook) {
// You can change these names to match the data in your workbook.
const TARGET_TABLE_NAME = 'TableNAdded';
const SOURCE_TABLE_NAME = 'TableN';
// Select what will be moved between tables.
const FILTER_COLUMN_INDEX = 27;
const FILTER_VALUE = 'Y';
// Get the Table objects.
let targetTable = workbook.getTable(TARGET_TABLE_NAME);
let sourceTable = workbook.getTable(SOURCE_TABLE_NAME);
// If either table is missing, report that information and stop the script.
if (!targetTable || !sourceTable) {
console.log(`Tables missing - Check to make sure both source (${TARGET_TABLE_NAME}) and target table (${SOURCE_TABLE_NAME}) are present before running the script. `);
return;
}
// Save the filter criteria currently on the source table.
const originalTableFilters = {};
// For each table column, collect the filter criteria on that column.
sourceTable.getColumns().forEach((column) => {
let originalColumnFilter = column.getFilter().getCriteria();
if (originalColumnFilter) {
originalTableFilters[column.getName()] = originalColumnFilter;
}
});
// Get all the data from the table.
const sourceRange = sourceTable.getRangeBetweenHeaderAndTotal();
const dataRows: (number | string | boolean)[][] = sourceTable.getRangeBetweenHeaderAndTotal().getValues();
// Create variables to hold the rows to be moved and their addresses.
let rowsToMoveValues: (number | string | boolean)[][] = [];
let rowAddressToRemove: string[] = [];
// Get the data values from the source table.
for (let i = 0; i < dataRows.length; i++) {
if (dataRows[i][FILTER_COLUMN_INDEX] === FILTER_VALUE) {
rowsToMoveValues.push(dataRows[i]);
// Get the intersection between table address and the entire row where we found the match. This provides the address of the range to remove.
let address = sourceRange.getIntersection(sourceRange.getCell(i, 0).getEntireRow()).getAddress();
rowAddressToRemove.push(address);
}
}
// If there are no data rows to process, end the script.
if (rowsToMoveValues.length < 1) {
console.log('No rows selected from the source table match the filter criteria.');
return;
}
console.log(`Adding ${rowsToMoveValues.length} rows to target table.`);
// Insert rows at the end of target table.
targetTable.addRows(-1, rowsToMoveValues)
// Remove the rows from the source table.
const sheet = sourceTable.getWorksheet();
// Remove all filters before removing rows.
sourceTable.getAutoFilter().clearCriteria();
// Important: Remove the rows starting at the bottom of the table.
// Otherwise, the lower rows change position before they are deleted.
console.log(`Removing ${rowAddressToRemove.length} rows from the source table.`);
rowAddressToRemove.reverse().forEach((address) => {
sheet.getRange(address).delete(ExcelScript.DeleteShiftDirection.up);
});
// Reapply the original filters.
Object.keys(originalTableFilters).forEach((columnName) => {
sourceTable.getColumnByName(columnName).getFilter().apply(originalTableFilters[columnName]);
});
}
I didn't even know where to start with this.
I have created a script that will move an entire row to another tab on the worksheet if certain text is entered into a selected cell.
I want to be able to do this if the cell is not empty rather than having certain text and I would like the row to be deleted all except the first column.
The script is below and works really well, I'm not great at coding and managed to cobble this together from some other scripts i found but i now can't manage to edit it to fit this new task.
I tried using Javascript not equals signs and other symbols and can remove rows that are empty but i can't seem to make it work.
function main(workbook: ExcelScript.Workbook) {
// You can change these names to match the data in your workbook.
const TARGET_TABLE_NAME = 'TableNAdded';
const SOURCE_TABLE_NAME = 'TableN';
// Select what will be moved between tables.
const FILTER_COLUMN_INDEX = 27;
const FILTER_VALUE = 'Y';
// Get the Table objects.
let targetTable = workbook.getTable(TARGET_TABLE_NAME);
let sourceTable = workbook.getTable(SOURCE_TABLE_NAME);
// If either table is missing, report that information and stop the script.
if (!targetTable || !sourceTable) {
console.log(`Tables missing - Check to make sure both source (${TARGET_TABLE_NAME}) and target table (${SOURCE_TABLE_NAME}) are present before running the script. `);
return;
}
// Save the filter criteria currently on the source table.
const originalTableFilters = {};
// For each table column, collect the filter criteria on that column.
sourceTable.getColumns().forEach((column) => {
let originalColumnFilter = column.getFilter().getCriteria();
if (originalColumnFilter) {
originalTableFilters[column.getName()] = originalColumnFilter;
}
});
// Get all the data from the table.
const sourceRange = sourceTable.getRangeBetweenHeaderAndTotal();
const dataRows: (number | string | boolean)[][] = sourceTable.getRangeBetweenHeaderAndTotal().getValues();
// Create variables to hold the rows to be moved and their addresses.
let rowsToMoveValues: (number | string | boolean)[][] = [];
let rowAddressToRemove: string[] = [];
// Get the data values from the source table.
for (let i = 0; i < dataRows.length; i++) {
if (dataRows[i][FILTER_COLUMN_INDEX] === FILTER_VALUE) {
rowsToMoveValues.push(dataRows[i]);
// Get the intersection between table address and the entire row where we found the match. This provides the address of the range to remove.
let address = sourceRange.getIntersection(sourceRange.getCell(i, 0).getEntireRow()).getAddress();
rowAddressToRemove.push(address);
}
}
// If there are no data rows to process, end the script.
if (rowsToMoveValues.length < 1) {
console.log('No rows selected from the source table match the filter criteria.');
return;
}
console.log(`Adding ${rowsToMoveValues.length} rows to target table.`);
// Insert rows at the end of target table.
targetTable.addRows(-1, rowsToMoveValues)
// Remove the rows from the source table.
const sheet = sourceTable.getWorksheet();
// Remove all filters before removing rows.
sourceTable.getAutoFilter().clearCriteria();
// Important: Remove the rows starting at the bottom of the table.
// Otherwise, the lower rows change position before they are deleted.
console.log(`Removing ${rowAddressToRemove.length} rows from the source table.`);
rowAddressToRemove.reverse().forEach((address) => {
sheet.getRange(address).delete(ExcelScript.DeleteShiftDirection.up);
});
// Reapply the original filters.
Object.keys(originalTableFilters).forEach((columnName) => {
sourceTable.getColumnByName(columnName).getFilter().apply(originalTableFilters[columnName]);
});
}
If I understand your question correctly, you are currently filtering the table if the value = "Y" (the value assigned to FILTER_VALUE). This part is happening here:
if (dataRows[i][FILTER_COLUMN_INDEX] === FILTER_VALUE) {
You'd like to update this line from checking if the cell value is Y to checking if the cell value is not empty. To do this, you can update this line like so:
if (dataRows[i][FILTER_COLUMN_INDEX] as string !== "") {
I'm trying to use the combination of Excel Office Script and Power Automate to send email with an image of Pivot Table.
Below is the code I came up with, but the resulting image that gets sent doesn't include the conditional formatting, only the data and the standard formatting get sent.
I even tried to recreate the conditional formatting within the script code, but no success.
Any ideas? Thanks!
function main(workbook: ExcelScript.Workbook): BudImg {
//Select Budget table
let selection = workbook.getWorksheet("Overview").getRange("A45:R59")
// Add a new worksheet
let sheet1 = workbook.addWorksheet("ScreenShotSheet");
//Paste to range A1 on sheet2 from range A20:J37 on selectedSheet
sheet1.getRange("A45").copyFrom(selection, ExcelScript.RangeCopyType.values, false, false);
sheet1.getRange("A45").copyFrom(selection, ExcelScript.RangeCopyType.formats, false, false);
//adjust columns
//sheet1.getRange("A:R").getFormat().autofitColumns();
//re-create conditional formatting
let conditionalFormatting: ExcelScript.ConditionalFormat;
conditionalFormatting = sheet1.getRange("K:R").addConditionalFormat(ExcelScript.ConditionalFormatType.cellValue);
conditionalFormatting.getCellValue().getFormat().getFont().setColor("#9C0006");
conditionalFormatting.getCellValue().getFormat().getFill().setColor("#FFC7CE");
conditionalFormatting.getCellValue().setRule({ formula1: "=0", formula2: undefined, operator: ExcelScript.ConditionalCellValueOperator.lessThan, });
//take screenshot
let table = sheet1.getRange("A45:R59");
let tableImg = selection.getImage();
//delete screenshotsheet
workbook.getWorksheet('ScreenShotSheet').delete();
return {tableImg};
}
interface BudImg {
tableImg: string
}
'''
To get an image of a pivot table, you need to have a line like the below:
workbook.getWorksheet("Sheet1").getPivotTable("My Pivot Table").getLayout().getRange().getImage();
Basically, you can specify the pivot table that you want using getPivotTable(id) and then you need to get the layout and the range of that layout. Then finally, you can use the getImage method. Hope that helps!
Your conditional formatting rule highlights the values which are equal to zero. You can just loop through the values of the range (K:R), see if they're zero, and if so, set the cells to the color you used in the conditional formatting. If you do it this way, the colors should be maintained when you create an image. You can see code to do that below:
function main(workbook: ExcelScript.Workbook) {
let sh: ExcelScript.Worksheet = workbook.getWorksheet("Sheet1")
let range: ExcelScript.Range = sh.getRange("K:R")
let vals: string[][] = range.getValues() as string[][]
let rowCount:number = range.getRowCount()
let colCount:number = range.getColumnCount()
for (let i = 0; i < rowCount; i++){
for (let j = 0; j < colCount; j++){
if (vals[i][j] as unknown === 0) {
let rang: ExcelScript.Range = sh.getRangeByIndexes(i,j,1,1)
rang.getFormat().getFont().setColor("#9C0006");
rang.getFormat().getFill().setColor("#FFC7CE");
}
}
}
}
I receive a daily Excel file that has around 15K rows and 22 columns. There are a few hundred different values in column A. I want to copy or move certain rows to predefined tabs. For example, everything with the value "Home" would be moved to Sheet2, "Work" would be moved to Sheet3 etc.
Any guides to for follow for this or ideas?
Here's my attempt at a script that gets the result I think you want. Note the use of the 'getUsedRange()' method. It helps you access ranges of various sizes. There are probably several other ways to do this in typescript/javascript or by doing the filtering with Excel APIs.
function main(workbook: ExcelScript.Workbook) {
//get the used range, assuming there is one worksheet
let worksheet = workbook.getActiveWorksheet();
let range = worksheet.getUsedRange();
let rangeValues = range.getValues();
// create an array to hold all the unique values in the first column skipping the header row
let firstCol: string[] = [];
rangeValues.forEach((curRow, index) => {
if (index > 0) {
firstCol.push(curRow[0].toString());
}
});
let uniqueFirstCol = firstCol.filter((val, ind, arr) => arr.indexOf(val) === ind);
// run thru all the unique values, filter as appropriate and add filtered values to new worksheets
uniqueFirstCol.forEach(v => {
let tempValues = rangeValues.filter((row, index) => row[0] === v);
let newSheet = workbook.addWorksheet(v);
let newRange = newSheet.getRangeByIndexes(1,0, tempValues.length, tempValues[0].length);
newRange.setValues(tempValues);
newSheet.getRangeByIndexes(0,0, 1, rangeValues[0].length).setValues([rangeValues[0]])
});
}
Summary: I have a multi-step script where I need an IF statement to handle a scenario when the range I am copying from could be empty. I need the script to move onto the next step even if it "skips" the immediate next step.
Scenario: A step in the middle of the script is to set several column filters and then copy the filtered results to another table. If the filtered results are blank/empty ,I want the script to skip the "copyfrom" step and move onto the next step which releases the previous filters that were set.
I attached a screenshot of the excel sample showing the filtered results are blank. The shown filters need to be released/cleared but the script is just stopping because the results are blank and it can't complete the immediate next step (copyfrom).
Script: I had help from another user on here with an IF statement in a different scenario, I tried to use that logic as my base for my current use case...but wasn't successful.
Here's the current script:
function main(workbook: ExcelScript.Workbook)
{
let target = workbook.getWorksheet('Target');
let source = workbook.getWorksheet('Source');
let targetTable = workbook.getTable('Target');
let sourceTable = workbook.getTable('Source');
const visibleRange = source.getUsedRange().getColumn(0);
let statusColumn = sourceTable.getColumnByName("Status");
let statusColumnRange = statusColumn.getRangeBetweenHeaderAndTotal();
//Identify last used row in Target sheet
const usedRange = target.getUsedRange();
console.log(usedRange.getAddress());
//Insert new row after last used row in Target sheet
const startCell = usedRange.getLastRow().getCell(0,0).getOffsetRange(1,0);
console.log(startCell.getAddress());
const targetRange = startCell.getResizedRange(0,0);
// Clear all filters on the table so that script filters can be applied
sourceTable.getAutoFilter().clearCriteria();
//Replace blanks with "null"
let emptyStatusCells = statusColumnRange.getSpecialCells(ExcelScript.SpecialCellType.blanks);
if (emptyStatusCells != undefined) {
let rangeAreas = emptyStatusCells.getAreas();
rangeAreas.forEach(range => {
let values = range.getValues();
values.forEach(cellValue => {
cellValue[0]= "null";
})
range.setValues(values);
})
//Clear Occurrence seq formula and re-apply
let sourceShiftedVisibleRangeFormula = visibleRange.getOffsetRange(1, 2);
let C2 = source.getRange('C2');
sourceShiftedVisibleRangeFormula.getUsedRange().clear();
C2.setFormula("=COUNTIF($A$2:A2,A2)");
//Filter Sources
const DuplicateFilter = 'Duplicate';
const ValueOfDuplicateFilter = 'Duplicate';
const OccurrenceFilter = 'Occurrence';
const ValueOfOccurrenceFilter = '1';
const IncludeInDupFilter = 'Include Dup Filter';
const ValueOfIncDupFilter = 'Yes';
//Find columns to filter
const duplicateFilter = sourceTable.getColumnByName(DuplicateFilter);
const occurrenceFilter = sourceTable.getColumnByName(OccurrenceFilter);
const includeDupFilter = sourceTable.getColumnByName(IncludeInDupFilter);
//Select values to filter by
duplicateFilter.getFilter().applyValuesFilter([ValueOfDuplicateFilter]);
occurrenceFilter.getFilter().applyValuesFilter([ValueOfOccurrenceFilter]);
includeDupFilter.getFilter().applyValuesFilter([ValueOfIncDupFilter]);
//Set source visible range to copy from
console.log(visibleRange.getAddress());
//const shiftedVisibleRange = visibleRange.getOffsetRange(1,0);
const sourceShiftedVisibleRange= visibleRange.getOffsetRange(1,4)
console.log(sourceShiftedVisibleRange.getAddress());
let sh = workbook.getActiveWorksheet();
let visTble = sh.getTable('Source');
let rv = visTble.getRangeBetweenHeaderAndTotal().getVisibleView();
if (rv.getRowCount() > 0){
let shiftedVisibleRange = visibleRange.getOffsetRange(1,0);
//Paste into Target table
targetRange.copyFrom(shiftedVisibleRange.getUsedRange(), ExcelScript.RangeCopyType.all, false,
false);
//Clear Occurrence filter to show all duplicate rows
occurrenceFilter.getFilter().clear();
//Set the Include Dup Filter string values to logged
let stringValue= "logged"
//Update include Dup Filter to logged for duplicate rows moved to target table during this
process
sourceShiftedVisibleRange.getUsedRange().setValue(stringValue);
//Clear all other filters setby script
sourceTable.getAutoFilter().clearCriteria();
}
}
This is the section of the script that I am struggling with, if empty it should skip the "Paste into Target table step" and move onto the next step called "Clear Occurrence filter to show all duplicate rows"
let sh = workbook.getActiveWorksheet();
let visTble = sh.getTable('Source');
let rv = visTble.getRangeBetweenHeaderAndTotal().getVisibleView();
if (rv.getRowCount() > 0){
let shiftedVisibleRange = visibleRange.getOffsetRange(1,0);
//Paste into Target table
targetRange.copyFrom(shiftedVisibleRange.getUsedRange(),
ExcelScript.RangeCopyType.all, false,
false);
//Clear Occurrence filter to show all duplicate rows
occurrenceFilter.getFilter().clear();
I think you can do this with your source table using the getVisibleView() method. Once you used that method, you could get the row count. Code that did that would look something like this:
function main(workbook: ExcelScript.Workbook){
let sh: ExcelScript.Worksheet = workbook.getActiveWorksheet();
let tbl: ExcelScript.Table = sh.getTable("Table1");
let rv: ExcelScript.RangeView = tbl.getRangeBetweenHeaderAndTotal().getVisibleView()
if (rv.getRowCount() > 0){
//more code here
}
}