💡 Note:
It's not possible for Apps Scripts to be automatically triggered by events in Google Drive.
The Drive API sends most of its notification payload as HTTP headers which cannot be accessed from a GAS Web App (doPost(e) only allows access to the POST body and URL params).
Introduction to Google Drive Activity API | Google for Developers
Track changes for users and shared drives | Google Drive | Google for Developers
Below are a few stand-alone Apps Scripts you can run from the Apps Script editor in a browser (NOT a mobile app).
/**
* Convert all files in a nominated folder to PDF in another nominated folder.
* Prerequisites: The folder must not have any standalone Apps Script files in it.
* Otherwise you will get: "Exception: Converting from application/vnd.google-apps.script to application/pdf is not supported."
*/
function convertAllFilesToPdfInFolder() {
const sourceFolderId = "1JXh8ldSLtVUTspx703Qw5DpKQIYTyL3c"; // Configure the source folder ID that has the Docs files.
const targetFolderId = "1pnkR4RcnXWjoIxPVi4_JczbPX-t8LRg9"; // Configure the target folder ID to output the PDF files.
const sourceFolder = DriveApp.getFolderById(sourceFolderId);
const targetFolder = DriveApp.getFolderById(targetFolderId);
const files = sourceFolder.getFiles();
while (files.hasNext()) {
const file = files.next();
const id = file.getId();
const document = DriveApp.getFileById(id);
const pdfBlob = document.getAs(MimeType.PDF);
// Create a PDF file in the target folder with the same name as the source document.
targetFolder.createFile(pdfBlob);
}
}
/**
* Convert all Sheets files in a nominated folder to XLSX in another nominated folder.
* Author: Mr Shane
* Version: 2025-04-12
*/
function convertSheetsToXlsxInFolder() {
const source = DriveApp.getFolderById("2pqnJPugjmHxaXsP8JWyn5uXIF3C0wjO3"); // Source folder containing Google Sheets files
const target = DriveApp.getFolderById("1dVjK3Rb259fPC6UgsQmLosI4ZzppTR8C"); // Target folder to save XLSX files
const token = ScriptApp.getOAuthToken(); // Get OAuth token for authorization
const files = source.getFilesByType(MimeType.GOOGLE_SHEETS); // Get only Google Sheets files from the source folder
while (files.hasNext()) {
const file = files.next(); // Get the next file
const url = `https://www.googleapis.com/drive/v3/files/${file.getId()}/export?mimeType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`; // Export URL for XLSX format
const response = UrlFetchApp.fetch(url, { headers: { Authorization: `Bearer ${token}` } }); // Fetch the exported XLSX file
target.createFile(response.getBlob().setName(file.getName() + ".xlsx")); // Save the XLSX file in the target folder with the same name
}
}
/**
* Count all files in Google Drive that are owned by the current user.
* Uses DriveApp.searchFiles to filter by ownership.
* Loops through each file while respecting the Apps Script time limit (~6 minutes).
* Author: Mr Shane
* Version: 2025-06-04
*/
function countMyDriveFiles() {
const startTime = Date.now(); // Capture the start time to enforce a time limit
const TIME_LIMIT = 300000; // Set time limit to 5 minutes (in milliseconds)
const files = DriveApp.searchFiles("'me' in owners"); // Get all files owned by the user
let count = 0; // Initialise file count
while (files.hasNext()) { // Loop while more files are available
files.next(); // Advance to the next file (no need to store it)
count++; // Increment the count
if (Date.now() - startTime > TIME_LIMIT) { // Check if time limit has been exceeded
Logger.log(`⏸️ Time limit reached. Counted so far: ${count}`); // Log partial count
return; // Exit the function to avoid exceeding execution time
}
}
Logger.log(`✅ Total files you own: ${count}`); // Log the final file count
}
/**
* Count all files in the designated folder
*/
function countAllFilesInFolder() {
const folderId = "1FWdYh7VpiLd1MI0Fuq9Xyysl2vVUpHLw"; // Configure the folder ID
const folder = DriveApp.getFolderById(folderId); // Get the folder object using the ID
let fileCount = 0; // Initialize a counter for the files
const files = folder.getFiles(); // Get an iterator for all files in the folder
while (files.hasNext()) { // Loop through the iterator as long as there are more files
files.next(); // Move to the next file (we don't need to store it, just count)
fileCount++; // Increment the file counter
}
Logger.log("Total files in folder: " + fileCount); // Log the total number of files
}
/**
* Delete resolved comments in a file
* Attention: The file should be refreshed after running this script.
* Prerequisites: 'Drive API v2' is enabled in Services.
* @see https://developers.google.com/workspace/drive/api/reference/rest/v2/comments
*/
function deleteResolvedComments_DriveAPIv2() {
const fileId = "1UqWEnUgtnCMkKICzBAPwmHpS4Ak2yZwyMXAQNB0tTY0"; // Configure the File ID of the file.
const comments = Drive.Comments.list(fileId).items;
comments.forEach(comment => {
if(comment.status === 'resolved'){
Drive.Comments.remove(fileId, comment.commentId);
}
});
}
/**
* Delete resolved comments in a file
* Attention: The file should be refreshed after running this script.
* Prerequisites: 'Drive API v3' is enabled in Services.
* @see https://developers.google.com/workspace/drive/api/reference/rest/v3/comments
*/
function deleteResolvedComments_DriveAPIv3() {
const fileId = "1UqWEnUgtnCMkKICzBAPwmHpS4Ak2yZwyMXAQNB0tTY0"; // Configure the File ID of the file.
const comments = Drive.Comments.list(fileId, { fields: "comments(id,resolved)" }).comments;
comments.forEach(comment => {
if (comment.resolved === true) {
Drive.Comments.remove(fileId, comment.id);
}
});
}
/**
* Send an email of a files version history
* To send the Timestamp, ID, and User from yesterdays File Version History
* Author: Mr Shane
* Version: 2023-03-09-01
* Prerequisites: Drive API to be enabled in the Apps Script Services.
*/
function sendRevisionMetadata() {
const fileId = "fileID"; // Enter the fileID
const output = [];
const timeZone = Session.getScriptTimeZone();
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
const revisions = Drive.Revisions.list(fileId, { 'maxResults': 1000 }); // https://developers.google.com/drive/api/v3/reference/revisions
revisions.items.forEach(revision => {
const revisionDate = new Date(revision.modifiedDate);
if (revisionDate.toDateString() === yesterday.toDateString()) {
const rowData = [
Utilities.formatDate(revisionDate, timeZone, 'yyyy-MM-dd HH:mm'),
` ${revision.id}`,
` ${revision.lastModifyingUser.displayName}`
];
output.push(rowData);
}
});
const recipient = "your_email_address"; // Enter your email address
const subject = "Revision Metadata for " + yesterday.toDateString();
const message = "Revision Metadata for " + yesterday.toDateString() + "\n\n" + createTable_(output);
MailApp.sendEmail(recipient, subject, message, {htmlBody: message, name: 'Revision Metadata'});
}
/**
* Helper function for setting the borders and padding around the cell data of the table.
*/
function createTable_(data) {
const tableHeader = '<tr><th style="padding: 8px; border: 1px solid black;">Timestamp</th><th style="padding: 8px; border: 1px solid black;">ID</th><th style="padding: 8px; border: 1px solid black;">User</th></tr>';
const tableRows = data.map(row => {
const tableCells = row.map(cell => `<td style="padding: 8px; border: 1px solid black;">${cell}</td>`).join('');
return `<tr style="border: 1px solid black;">${tableCells}</tr>`;
}).join('');
return `<table style="border-collapse: collapse;">${tableHeader}${tableRows}</table>`;
}
/**
* Three methods of retrieving details of files shared with you:
* 1. Using Drive API v2 (enabled in Apps Script Services)
* 2. Using Drive API v3 (enabled in Apps Script Services)
* 3. Without enabling Drive API, by calling the v3 REST endpoint via UrlFetchApp
*/
/**
* Log the details of all files 'Shared with me' using Drive API v2
* Author: Mr Shane
* Version: 2025-06-15
* Prerequisites:
* > Drive API v2 is enabled
* > Script is bound to a Sheets file
* > The target sheet for the results has at least 10 columns (column I).
* @see https://developers.google.com/workspace/drive/api/reference/rest/v2/files
*/
function sharedWithMe_DriveAPIv2() {
const CHUNK_SIZE = 500; // Configure the maximum number of files to process per run. (Recommended: 500)
const FUNCTION_NAME = "sharedWithMe_DriveAPIv2"; // Configure this with the name of the same function
const SHEET_NAME = "Shared With Me"; // Configure the name of the sheet to write results to
const PAGE_TOKEN_KEY = "SWM_PAGE_TOKEN"; // Configure the 'Property' key used to store Drive pageToken
const props = PropertiesService.getScriptProperties(); // Access script properties
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME); // Get the target sheet
let pageToken = props.getProperty(PAGE_TOKEN_KEY); // Retrieve page token to continue from last spot
let totalProcessed = 0; // Counter to track number of files processed this run
const rows = []; // Collect rows to write to the sheet
if (!pageToken) { // If no page token, this is a fresh start
sheet.clearContents(); // Clear any existing content
sheet.appendRow([ // Add headers to the sheet
"File ID", "File Name", "Date Created", "Owner Name", "Owner Email",
"Editor Emails", "Viewer Emails", "Containing Folder", "File URL"
]);
}
while (totalProcessed < CHUNK_SIZE) { // Loop until chunk size is reached
const params = { // Call Drive API v2 parameters to list shared files
q: "sharedWithMe = true", // Query for files shared with me
maxResults: 100, // Max files per API call (limit: 100)
pageToken: pageToken, // Use saved token to continue from last run
fields: "nextPageToken, items(id, title, createdDate, owners(displayName,emailAddress), permissions(emailAddress,role), parents(id), alternateLink)" // Specify fields to retrieve/return
};
const response = Drive.Files.list(params); // Make the Drive API call
const files = response.items || []; // Get array of items or fallback to empty
if (files.length === 0) break; // Exit if no files found
for (const file of files) { // Loop through each file
const owner = file.owners?.[0] || {}; // Get first owner or fallback to empty
const editorEmails = (file.permissions || []) // Get editor emails
.filter(p => p.role === "writer" && p.emailAddress) // Filter for writers with email
.map(p => p.emailAddress) // Extract email addresses
.join('\n'); // Join emails with line breaks
const viewerEmails = (file.permissions || []) // Get viewer emails
.filter(p => p.role === "reader" && p.emailAddress) // Filter for readers with email
.map(p => p.emailAddress) // Extract email addresses
.join('\n'); // Join emails with line breaks
let parentName = ""; // Initialize parent folder name
const parentId = (file.parents || [])[0]?.id; // Get first parent ID if available
if (parentId) { // If parent exists
try {
const parent = DriveApp.getFolderById(parentId); // Try getting the folder
parentName = parent.getName(); // Get folder name
} catch (e) {
parentName = "(Unknown or Inaccessible)"; // If error, fallback
}
}
rows.push([
file.id, // File ID
file.title, // File Name
file.createdDate, // Created Date
owner.displayName || "", // Owner Name
owner.emailAddress || "", // Owner Email
editorEmails, // Editor Emails
viewerEmails, // Viewer Emails
parentName, // Containing Folder Name
file.alternateLink // Link to file
]);
totalProcessed++; // Increment file counter
if (totalProcessed >= CHUNK_SIZE) break; // Stop if chunk limit reached
}
if (response.nextPageToken) { // If there are more files
pageToken = response.nextPageToken; // Save token for next run
} else {
pageToken = null; // No more files to process
break; // Exit loop
}
}
if (rows.length > 0) {
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows); // Write results to sheet
}
if (pageToken) { // If more data remains
props.setProperty(PAGE_TOKEN_KEY, pageToken); // Save token for future runs
create5MinTrigger?.(FUNCTION_NAME); // Optional: create 5-min trigger if defined
Logger.log(`Paused: ${totalProcessed} files processed. More remain.`); // Log progress
} else {
props.deleteProperty(PAGE_TOKEN_KEY); // Remove stored token
deleteTrigger?.(FUNCTION_NAME); // Optional: delete time-based trigger if defined
Logger.log(`Finished: ${totalProcessed} files processed. All done.`); // Log completion
}
}
/**
* Log the details of all files 'Shared with me' using Drive API v3
* Author: Mr Shane
* Version: 2025-06-15
* Prerequisites:
* > Drive API v3 is enabled
* > Script is bound to a Sheets file
* > The target sheet for the results has at least 10 columns (column I).
* @see https://developers.google.com/workspace/drive/api/reference/rest/v3/files
*/
function sharedWithMe_DriveAPIv3() {
const CHUNK_SIZE = 500; // Configure the maximum number of files to process per run. (Recommended: 500)
const FUNCTION_NAME = "sharedWithMe_DriveAPIv3"; // Configure this with the name of the same function
const SHEET_NAME = "Shared With Me"; // Configure the name of the sheet to write results to
const PAGE_TOKEN_KEY = "SWM_PAGE_TOKEN"; // Configure the 'Property' key used to store Drive pageToken
const props = PropertiesService.getScriptProperties(); // Access script properties
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME); // Get the target sheet
let pageToken = props.getProperty(PAGE_TOKEN_KEY); // Retrieve page token to continue from last spot
let totalProcessed = 0; // Counter to track number of files processed this run
const rows = []; // Collect rows to write to the sheet
if (!pageToken) { // If no page token, this is a fresh start
sheet.clearContents(); // Clear any existing content
sheet.appendRow([ // Add headers to the sheet
"File ID", "File Name", "Date Created", "Owner Name", "Owner Email",
"Editor Emails", "Viewer Emails", "Containing Folder", "File URL"
]);
}
while (totalProcessed < CHUNK_SIZE) { // Loop until chunk size is reached
const params = { // Call Drive API v3 parameters to list shared files
q: "sharedWithMe = true", // Query for files shared with me
pageSize: 100, // Max files per API call (limit: 100)
pageToken: pageToken, // Use saved token to continue from last run
fields: "nextPageToken, files(id, name, createdTime, owners(displayName,emailAddress), permissions, parents, webViewLink)" // Specify fields to retrieve/return
};
const response = Drive.Files.list(params); // Make the Drive API call
const files = response.files || []; // Get array of files or fallback to empty
if (files.length === 0) break; // Exit if no files found
for (const file of files) { // Loop through each file
const owner = file.owners?.[0] || {}; // Get first owner or fallback to empty
const editorEmails = (file.permissions || []) // Get editor emails
.filter(p => p.role === "writer" && p.emailAddress) // Filter for writers with email
.map(p => p.emailAddress) // Extract email addresses
.join('\n'); // Join emails with line breaks
const viewerEmails = (file.permissions || []) // Get viewer emails
.filter(p => p.role === "reader" && p.emailAddress) // Filter for readers with email
.map(p => p.emailAddress) // Extract email addresses
.join('\n'); // Join emails with line breaks
let parentName = ""; // Initialize parent folder name
const parentId = (file.parents || [])[0]; // Get first parent ID if available
if (parentId) { // If parent exists
try {
const parent = DriveApp.getFolderById(parentId); // Try getting the folder
parentName = parent.getName(); // Get folder name
} catch (e) {
parentName = "(Unknown or Inaccessible)"; // If error, fallback
}
}
rows.push([ // Add data row for this file
file.id, // File ID
file.name, // File Name
file.createdTime, // Created Date
owner.displayName || "", // Owner Name
owner.emailAddress || "", // Owner Email
editorEmails, // Editor Emails
viewerEmails, // Viewer Emails
parentName, // Containing Folder Name
file.webViewLink // Link to file
]);
totalProcessed++; // Increment file counter
if (totalProcessed >= CHUNK_SIZE) break; // Stop if chunk limit reached
}
if (response.nextPageToken) { // If there are more files
pageToken = response.nextPageToken; // Save token for next run
} else {
pageToken = null; // No more files to process
break; // Exit loop
}
}
if (rows.length > 0) { // If any rows were gathered
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows); // Write to sheet
}
if (pageToken) { // If more data remains
props.setProperty(PAGE_TOKEN_KEY, pageToken); // Save token for future runs
create5MinTrigger?.(FUNCTION_NAME); // Optional: create 5-min trigger if defined
Logger.log(`Paused: ${totalProcessed} files processed. More remain.`); // Log progress
} else {
props.deleteProperty(PAGE_TOKEN_KEY); // Remove stored token
deleteTrigger?.(FUNCTION_NAME); // Optional: delete time-based trigger if defined
Logger.log(`Finished: ${totalProcessed} files processed. All done.`); // Log completion
}
}
/**
* Log the details of all files 'Shared with me' calling the v3 REST endpoint via UrlFetchApp
* Author: Mr Shane
* Version: 2025-06-15
* Prerequisites:
* > Script is bound to a Sheets file
* > The target sheet for the results has at least 10 columns (column I).
* @see https://developers.google.com/workspace/drive/api/reference/rest/v3/files
*/
function sharedWithMe_UrlFetchApp_Drivev3() {
const CHUNK_SIZE = 500; // Max number of files to process per run
const FUNCTION_NAME = "sharedWithMe_UrlFetchApp_Drivev3"; // Configure this with the name of the same function
const SHEET_NAME = "Shared With Me"; // Sheet name to write results to
const PAGE_TOKEN_KEY = "SWM_PAGE_TOKEN"; // Key for storing the Drive pageToken
const props = PropertiesService.getScriptProperties(); // Access Script Properties storage
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME); // Get target sheet by name
let pageToken = props.getProperty(PAGE_TOKEN_KEY); // Retrieve stored page token
let totalProcessed = 0; // Initialize counter for processed files
const rows = []; // Array to collect row data for writing to sheet
if (!pageToken) { // If this is the first run (no token saved)
sheet.clearContents(); // Clear previous sheet contents
sheet.appendRow(["File ID", "File Name", "Date Created", "Owner Name", "Owner Email", "Editor Emails", "Viewer Emails", "Containing Folder", "File URL"]); // Add header row
}
while (totalProcessed < CHUNK_SIZE) { // Keep looping until chunk limit is reached
const accessToken = ScriptApp.getOAuthToken(); // Get current user's OAuth token
let url = "https://www.googleapis.com/drive/v3/files"; // Base URL for Drive v3 files endpoint
const params = { // Parameters for Drive API query
q: "sharedWithMe = true", // Only get files shared with the user
pageSize: 100, // Max items per request
fields: "nextPageToken, files(id, name, createdTime, owners(displayName,emailAddress), permissions(emailAddress,role), parents, webViewLink)", // Fields to return
supportsAllDrives: true // Include shared drives
};
if (pageToken) params.pageToken = pageToken; // Add page token if continuing from previous batch
url += "?" + Object.entries(params).map(([key, val]) => `${key}=${encodeURIComponent(val)}`).join("&"); // Construct full URL with query params
const response = UrlFetchApp.fetch(url, { // Make API call
headers: { Authorization: `Bearer ${accessToken}` }, // Include OAuth token in request header
muteHttpExceptions: true // Prevent script from halting on non-200 errors
});
const json = JSON.parse(response.getContentText()); // Parse JSON response
const files = json.files || []; // Get files array or default to empty
if (files.length === 0) break; // Exit if no files returned
for (const file of files) { // Iterate through each file
const owner = file.owners?.[0] || {}; // Get first owner or default to empty object
const editorEmails = (file.permissions || []).filter(p => p.role === "writer" && p.emailAddress).map(p => p.emailAddress).join('\n'); // Collect editor emails
const viewerEmails = (file.permissions || []).filter(p => p.role === "reader" && p.emailAddress).map(p => p.emailAddress).join('\n'); // Collect viewer emails
let parentName = ""; // Placeholder for folder name
const parentId = file.parents?.[0]; // Get first parent ID if it exists
if (parentId) { // If parent exists
try {
const parent = DriveApp.getFolderById(parentId); // Try to get folder by ID
parentName = parent.getName(); // Get folder name
} catch (e) {
parentName = "(Unknown or Inaccessible)"; // Fallback name if folder can't be accessed
}
}
rows.push([ // Add row of file info to array
file.id, // File ID
file.name, // File name
file.createdTime, // Creation date
owner.displayName || "", // Owner name
owner.emailAddress || "", // Owner email
editorEmails, // Editor email(s)
viewerEmails, // Viewer email(s)
parentName, // Containing folder name
file.webViewLink // URL to open file
]);
totalProcessed++; // Increment file count
if (totalProcessed >= CHUNK_SIZE) break; // Stop if chunk size reached
}
if (json.nextPageToken) { // If there’s another page of results
pageToken = json.nextPageToken; // Save token for next batch
} else {
pageToken = null; // No more pages, finish processing
break;
}
}
if (rows.length > 0) { // If there’s new data to write
sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows); // Write rows to sheet
}
if (pageToken) { // If there's more data to process later
props.setProperty(PAGE_TOKEN_KEY, pageToken); // Save token for next run
create5MinTrigger(FUNCTION_NAME); // Set trigger to resume later
Logger.log(`Paused: ${totalProcessed} files processed. More remain.`); // Log progress
} else {
props.deleteProperty(PAGE_TOKEN_KEY); // Clear stored token
deleteTrigger(FUNCTION_NAME); // Remove timer trigger
Logger.log(`Finished: ${totalProcessed} files processed. All done.`); // Log completion
}
}
/** HELPER functions to create a trigger to repeatedly run the function until the task is complete */
/**
* Creates a time-based trigger to run the given function every 5 minutes.
* @param {string} functionName - The name of the function to trigger.
*/
function create5MinTrigger(functionName) {
const existingTriggers = ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() === functionName); // Check for existing triggers
if (existingTriggers.length === 0) {
ScriptApp.newTrigger(functionName).timeBased().everyMinutes(5).create(); // Create new 5-min trigger
}
}
/**
* Deletes all time-based triggers associated with the given function.
* @param {string} functionName - The name of the function whose triggers should be deleted.
*/
function deleteTrigger(functionName) {
const triggers = ScriptApp.getProjectTriggers(); // Get all project triggers
for (const trigger of triggers) {
if (trigger.getHandlerFunction() === functionName) {
ScriptApp.deleteTrigger(trigger); // Delete matching trigger
}
}
}
/** END OF SCRIPT */
/**
* Log the status of comments in a file (status = 'open' or 'resolved')
* Author: Mr Shane
* Version: 2025-06-01
* Prerequisites: 'Drive API v2' is enabled in Services.
* @see https://developers.google.com/workspace/drive/api/reference/rest/v2/comments
*/
function logCommentStatus_DriveAPIv2() {
const fileId = "1UqWEnUgtnCMkKICzBAPwmHpS4Ak2yZwyMXAQNB0tTY0"; // Configure the File ID of the file.
const comments = Drive.Comments.list(fileId).items;
comments.forEach(comment => { console.log(comment.commentId, comment.status, comment.content) } );
}
/**
* Log the status of comments in a file (resolved = 'true' or 'undefined')
* Author: Mr Shane
* Version: 2025-06-01
* Prerequisites: 'Drive API v3' is enabled in Services.
* @see https://developers.google.com/workspace/drive/api/reference/rest/v3/comments
*/
function logCommentStatus_DriveAPIv3() {
const fileId = "1UqWEnUgtnCMkKICzBAPwmHpS4Ak2yZwyMXAQNB0tTY0"; // Configure the File ID of the file.
const comments = Drive.Comments.list(fileId, { fields: "comments(id,resolved, content)" }).comments;
comments.forEach(comment => { console.log(comment.id, comment.resolved, comment.content) } );
}
/**
* Log the full version history of a file,
* including revision ID, modification date, user, size, and export links.
* Useful for audit trails or identifying changes over time.
* Author: Mr Shane
* Version 2025-05-16
* Prerequisites: 'Drive API v3' is enabled in Services.
* @see https://developers.google.com/workspace/drive/api/reference/rest/v3/revisions/list
*/
function logRevisionsDriveV3() {
const fileId = "YOUR_FILE_ID_HERE"; // Configure the File ID of the source Google Docs/Forms/Sheets/Slides/Vids file.
const response = Drive.Revisions.list(fileId, { // Call Drive API v3 to list revisions
pageSize: 100, // Limit to 100 revisions per request
fields: 'revisions(id, modifiedTime, lastModifyingUser(displayName,emailAddress), exportLinks)' // Request only specific fields including exportLinks
});
const revisions = response.revisions; // Extract the revisions array from the API response
if (!revisions || revisions.length === 0) { // Check if no revisions were returned
Logger.log('No revisions found.'); // Log that there are no revisions
return; // Exit the function
}
Logger.log(`Total revisions: ${revisions.length}`); // Log the number of revisions found
revisions.forEach((rev, index) => { // Loop through each revision
let logEntry = `--- Revision #${index + 1} ---\n`; // Start a log entry with the revision number
logEntry += `ID: ${rev.id}\n`; // Add the revision ID
logEntry += `Modified Time: ${rev.modifiedTime || 'Unknown'}\n`; // Add the modification time or 'Unknown'
logEntry += `Modified By: ${rev.lastModifyingUser?.displayName || 'Unknown'} (${rev.lastModifyingUser?.emailAddress || 'N/A'})\n`; // Add display name and email of modifying user
if (rev.exportLinks) { // If exportLinks are available
logEntry += `Export Links:\n`; // Add a heading for export links
for (const [type, url] of Object.entries(rev.exportLinks)) { // Loop through each export format and its URL
logEntry += ` - ${type}: ${url}\n`; // Add each export type and link
}
} else { // If exportLinks are not available
logEntry += `Export Links: None or not available in v3\n`; // Note unavailability of export links
}
logEntry += `-----------------------------`; // Add a separator
Logger.log(logEntry); // Log the complete revision entry
});
}
/**
* Replace a file with its copy.
* This script serves as a workaround for a Google Drive issue where a PDF may remain accessible
* via its source link even after sharing is revoked or the file is moved to Trash.
* Author: Mr Shane
* Version: 2025-05-09
*
* Prerequisites:
* 1. Give the script project a meaningful name (instead of "Untitled project")
* 2. 'Drive API' enabled in Services.
* 3. Configure ScriptProperties to store the source file ID under the key "sourceFileId".
* → Go to Project settings > Script Properties
* → Create a Property called sourceFileId with its value being the file ID of the source file.
*/
function replaceFileWithCopy() {
const props = PropertiesService.getScriptProperties(); // Get the script properties service
const sourceFileId = props.getProperty("sourceFileId"); // Get the source file ID from script properties
if (!sourceFileId) throw new Error('No "sourceFileId" property found in ScriptProperties.'); // Throw error if property is missing
const sourceFile = DriveApp.getFileById(sourceFileId); // Get the source file using its ID
const sourceName = sourceFile.getName(); // Store the source file name
const sourceParent = sourceFile.getParents().next(); // Get the parent folder (assumes only one parent)
const copiedFile = sourceFile.makeCopy(sourceParent); // Make a copy of the source file
Drive.Files.remove(sourceFileId); // Permanently delete the source file
copiedFile.setName(sourceName); // Rename the copy to match source
copiedFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); // Set sharing to "anyone with the link can view"
props.setProperty("sourceFileId", copiedFile.getId()); // Update the property with the new file ID
Logger.log("New file ID: " + copiedFile.getId()); // Log new file ID for visibility (optional)
}