1

The goal is to create frozen backups of a spreadsheet with multiple sheets. The backup spreadsheet needs to copy over values and format, but not the formulas.

Some of the sheets have =IMPORTRANGE, which becomes a problem if the spreadsheet is copied first and then the values copied over the same range, again, to get rid of formulas, because the new spreadsheet requires access to the imported range in the middle of the function (which leads to #REF in A1).

This question was clearly asked many times, but none of the answers seem to solve this issue. I have looked at these questions (and more, actually): Link 1, Link 2, Link 3, Link 4, Link 5, and Link 6. I tried implementing snippets from the answers to those questions or combinations of them to no avail. It became to difficult to track every bit of snippet I tested.

I tried using copyTo() either to copy entire sheets in a forEach function, copy with the options {contentsOnly:true} and {formatOnly:true}, but have been unsuccessful. Either copyTo() wants the ranges to be in the same spreadsheet/sheet or the getDataRange() doesn't match the backup range...

Here's the script I'm currently using, which successfully creates a copy of the entire spreadsheet with values only. I cannot recall from which question I got it from.

function copyEntireSpreadsheet(){
  var ss,ssName,sheet,sheetName,data,destination
  ss = SpreadsheetApp.openById("id").getSheets();
  ssName = SpreadsheetApp.openById("id").getName();
  destination = SpreadsheetApp.create(ssName + " - " + new Date().toLocaleString());

  for (var i = 0; i < ss.length; i++){
    sheet = ss[i];
    sheetName = sheet.getSheetName();
    data = sheet.getSheetValues(1, 1, sheet.getLastRow(), sheet.getLastColumn());
    destination.insertSheet(sheetName);
    destination.getSheets()[i+1].getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn()).setValues(data);
  }

 destination.deleteSheet(destination.getSheetByName('Sheet1'));
}

The source spreadsheet has a lot of formatting with merged cells, conditional formatting, etc., which would be ideal to copy. I can also hard code certain sheets into the script, if that would make any difference with an alternative method.

In short: I'm looking for a frozen backup of a spreadsheet with multiple sheets. Values and formatting need to be copied, not the formulas.

I might be missing something in one of the answers to all those questions, so I'll keep trying. In the meantime, any help/direction would be appreciated. Thanks in advance!

SOLVED: @Tanaike's second sample script below is an excellent workaround to copying sheets that have references to other spreadsheets and hence require access to them before being overwritten by values only. Big thanks to @Tanaike for the extensive help with this -- much appreciated.

BearsBeetsBSG
  • 55
  • 1
  • 9

1 Answers1

4
  • You want to copy the values and format of all sheets in a Spreadsheet to new Spreadsheet.
    • In this case, you want to copy only values without the formulas.
    • The source spreadsheet has a lot of formatting with merged cells, conditional formatting, etc.
    • The source Spreadsheet includes the values put with IMPORTRANGE.
  • You want to achieve this using Google Apps Script.

The flow of sample script for above goal is as follows.

Flow:

  1. Copy all sheets in the source Spreadsheet as the temporal sheets.
  2. At the copied sheets, the cells are overwritten by only the values. By this, the formulas can be removed.
    • If the source Spreadsheet is copied, the values retrieved with IMPORTRANGE become #REF. Because it is required to authorize at the new copied Spreadsheet. In order to avoid this, the temporal sheets are copied in the source Spreadsheet.
  3. Copy the source Spreadsheet.
  4. Delete the temporal sheets in the source Spreadsheet.
  5. Delete the original sheets in the destination Spreadsheet.

By above flow, only the values can be copied without the authorization of IMPORTRANGE.

Sample script:

function copyEntireSpreadsheet() {
  var id = "###"; // Please set the source Spreadsheet ID.

  var ss = SpreadsheetApp.openById(id);
  var srcSheets = ss.getSheets();
  var tempSheets = srcSheets.map(function(sheet, i) {
    var sheetName = sheet.getSheetName();
    var dstSheet = sheet.copyTo(ss).setName(sheetName + "_temp");
    var src = dstSheet.getDataRange();
    src.copyTo(src, {contentsOnly: true});
    return dstSheet;
  });
  var destination = ss.copy(ss.getName() + " - " + new Date().toLocaleString());
  tempSheets.forEach(function(sheet) {ss.deleteSheet(sheet)});
  var dstSheets = destination.getSheets();
  dstSheets.forEach(function(sheet) {
    var sheetName = sheet.getSheetName();
    if (sheetName.indexOf("_temp") == -1) {
      destination.deleteSheet(sheet);
    } else {
      sheet.setName(sheetName.slice(0, -5));
    }
  });
}

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Thank you so much, @Tanaike. Your sample script is a clever way of going around the issue with temporary sheets! It almost perfectly works, except that it is unable to get the values for a sheet that has `=IMPORTRANGE` in `A1`. That source sheet will have a couple thousand rows in columns `A:X`. It might be that the script is processing the sheets too fast for `IMPORTRANGE` to pull the data into the temporary sheet that's created, so the only value is `#REF` in `A1` of the temporary sheet when it's copied to the new spreadsheet. – BearsBeetsBSG Oct 07 '19 at 11:55
  • Maybe I can copy the formula, freeze the source sheet, copy the frozen source sheet into the new spreadsheet, clear the source sheet, and re-write the formula into the source sheet? This would be quite a workaround, unless there's an easier way to pause the script to give time for the formulas to work? – BearsBeetsBSG Oct 07 '19 at 11:56
  • @BearsBeetsBSG Thank you for replying. In your question, I understood that you want to copy only the values and format of all sheets in a Spreadsheet to new Spreadsheet. But in your replying, you say `Maybe I can copy the formula, freeze the source sheet, copy the frozen source sheet into the new spreadsheet, clear the source sheet, and re-write the formula into the source sheet?`. So I'm confused. Can you explain about the detail of your goal? If my answer was not useful for your situation, I have to apologize. – Tanaike Oct 07 '19 at 22:38
  • Apologies for any confusion. Your sample script works almost perfectly, except for sheets that have `=IMPORTRANGE`. I'm guessing that, as the script loops through sheets, the temporary sheet that's created in the source spreadsheet doesn't have enough time to pull the `IMPORTRANGE` data before getting copied over to the new spreadsheet. The part of my comment you quoted was me thinking out loud to overcome this issue. In sum: The remaining thing is copying sheets with `IMPORTRANGE`, because right now, with your sample script, the new copied sheet has `#REF`. Does that make sense? – BearsBeetsBSG Oct 07 '19 at 22:44
  • @BearsBeetsBSG Thank you for replying. If my understanding is correct, for example, how about putting `SpreadsheetApp.flush()` and `Utilities.sleep(seconds)` after the line of `var destination = ss.copy(ss.getName() + " - " + new Date().toLocaleString());`? In this case, please adjust `seconds` for your situation. But I'm not sure whether this is the direct solution of your current issue. So if this was not useful, I have to apologize. At that time, can you provide a sample Spreadsheet? – Tanaike Oct 07 '19 at 22:49
  • I'm sorry, @Tanaike. I got confused. I thought the script was creating temporary copies of sheets in the `source` spreadsheet with values and formatting, and then copying those temporary sheets to a new blank spreadsheet. Currently, as I described in my question, when we use `ss.copy()` to copy the spreadsheet first, the second spreadsheet needs access for the `IMPORTRANGE` to work before the formula is gone. The script continues to write values, so before `IMPORTRANGE` can work, `#REF` becomes a value, because we can grant access in the middle of the function. – BearsBeetsBSG Oct 08 '19 at 01:23
  • I'm trying to figure out if it's possible to do it this way: (1) Create temp copy of a sheet with values and formatting only (no formulas) in the source spreadsheet, (2) copy that sheet to a new, blank spreadsheet, (3) delete the temporary sheet in the source spreadsheet. This way could avoid the access issue for `IMPORTRANGE` since the first temp copy is on the same spreadsheet with values+formatting only. – BearsBeetsBSG Oct 08 '19 at 01:27
  • @BearsBeetsBSG Thank you for replying. If your test didn't resolve your issue, can you provide a sample Spreadsheet for replicating your issue? By this, I would like to confirm it and think of the solution. Of course, please remove your personal information. – Tanaike Oct 08 '19 at 04:23
  • @BearsBeetsBSG Thank you for providing the sample Spreadsheet. Using the sample Spreadsheet, I added one more sample script. Could you please confirm it? In my environment, I could confirm that the formulas are removed and only the values are put. If this was not the result you want, I apologize. – Tanaike Oct 08 '19 at 22:40
  • Thank you for trying so much, @Tanaike. Unfortunately, because the files I shared with you were publicly accessible via the links, the second sheet with `IMPORTRANGE` works when the spreadsheet is copied (because the source spreadsheet for the `IMPORTRANGE` is accessible). However, when the spreadsheets are not publicly shared, `IMPORTRANGE` requires access before pulling the data onto the sheet, but the formula goes away before it has access to the other spreadsheet, so the only value left is #REF. If you try now with the spreadsheet I shared with you, it won't work. – BearsBeetsBSG Oct 08 '19 at 23:01
  • @BearsBeetsBSG Thank you for replying. I could correctly understand about your situation. So I updated my answer. Could you please confirm it? – Tanaike Oct 08 '19 at 23:38
  • That's it!! Exactly what I had in mind, but could not figure out how to go about it. Works perfectly. Tested with a much larger spreadsheet, as well. Thank you so very much, @Tanaike! – BearsBeetsBSG Oct 08 '19 at 23:47
  • @BearsBeetsBSG In order to easy to read, I updated my answer with the final script. – Tanaike Oct 09 '19 at 08:01
  • 1
    Thank you, again, for your help. Edit looks great. :) – BearsBeetsBSG Oct 09 '19 at 11:05