

Google Drive: Copy File
This item copies the specified file on Google Drive. You can optionally specify the destination folder and the new file name, and store the ID or web view URL of the created file.
Basic Configs
- Step Name
- Note
Configs for this Auto Step
- conf_Auth
- C1: Service Account Setting *
- conf_SourceFileId
- C2: Source File ID *
- conf_DestinationFolderId
- C3: Folder ID to store (The same folder as the source if blank)
- conf_NewFileName
- C4: New File Name (named automatically if blank)#{EL}
- conf_NewFileIdItem
- C5: Data item to save new File ID
- conf_NewFileUrlItem
- C6: Data item to save new File Web View URL
Notes
- The files that will be downloaded must be shared in advance with the service account set in [C1: Service Account Setting]
- To set up [C1: Service Account Setting] :
- Prepare a service account on Google Cloud Console
- Create or upload a service account key
- If you create a service account key on Google Cloud Console, you can download a JSON file containing the necessary information
- Create or upload a service account key
- Create an OAuth2 JWT Bearer setting on Questetra BPM Suite and set it to C1
- Scope
https://www.googleapis.com/auth/driveis required - Set up other items as shown in the table below :
- Scope
- Prepare a service account on Google Cloud Console
| Settings on Questetra BPM Suite | Corresponding Information of Google Cloud Service Account Key | Property Name in JSON File | Required or Not |
|---|---|---|---|
| Client ID | OAuth2 Client ID | client_id | Not Required |
| Private Key ID | Key ID | private_key_id | Required |
| Private Key | Private Key | private_key | Required |
| Custom Secret Information 1 | Email Address | client_email | Required |

Errors
"code": 403, "message": "Google Drive API has not been used in project 123456789012 before or it is disabled.".- ‘Google Drive API’ needs to be enabled
"code": 404, "message": "File not found:"- The file may not exist
- ‘Google Service Account’ may not have permission to read the file
Capture

See Also
Script (click to open)
- An XML file that contains the code below is available to download
- google-drive-file-copy.xml (C) Questetra, Inc. (MIT License)
- If you are using Professional, you can modify the contents of this file and use it as your own add-on auto step
const COPY_FIELDS = 'id,name,parents,webViewLink';
function main() {
const auth = configs.getObject('conf_Auth');
const sourceFileId = getSourceFileId();
const destinationFolderId = getDestinationFolderId();
const newFileName = getNewFileName();
const newFileIdDataDef = configs.getObject('conf_NewFileIdItem');
const newFileUrlDataDef = configs.getObject('conf_NewFileUrlItem');
const copiedFile = copyFile(auth, sourceFileId, destinationFolderId, newFileName);
engine.log(`Succeeded to copy. Source ID: ${sourceFileId}, New ID: ${copiedFile.id}`);
if (newFileIdDataDef !== null) {
engine.setData(newFileIdDataDef, copiedFile.id);
}
if (newFileUrlDataDef !== null) {
const webViewUrl = copiedFile.webViewLink ?? `https://drive.google.com/file/d/${copiedFile.id}/view`;
engine.setData(newFileUrlDataDef, webViewUrl);
}
}
/**
* コピー元のファイル ID を取得する
* @return {String} コピー元ファイル ID
*/
function getSourceFileId() {
let fileId = configs.get('conf_SourceFileId');
const fileIdDef = configs.getObject('conf_SourceFileId');
if (fileIdDef !== null) {
fileId = engine.findData(fileIdDef);
}
if (fileId === null) {
throw "Source File ID isn't set.";
}
const trimmed = fileId.trim();
if (trimmed === '') {
throw "Source File ID isn't set.";
}
return trimmed;
}
/**
* 保存先フォルダ ID を取得する(未指定の場合は空文字を返す)
* @return {String} 保存先フォルダ ID または空文字
*/
function getDestinationFolderId() {
let folderId = configs.get('conf_DestinationFolderId');
const folderIdDef = configs.getObject('conf_DestinationFolderId');
if (folderIdDef !== null) {
folderId = engine.findData(folderIdDef);
}
if (folderId === null) {
return '';
}
return folderId.trim();
}
/**
* 新しいファイル名を取得する(未指定の場合は空文字を返す)
* @return {String} 新しいファイル名または空文字
*/
function getNewFileName() {
const newName = configs.get('conf_NewFileName');
if (newName === null) {
return '';
}
return newName.trim();
}
/**
* ファイルをコピーする
* @param {HttpAuthSettingWrapper} auth 認証設定
* @param {String} sourceFileId コピー元のファイル ID
* @param {String} destinationFolderId 保存先フォルダ ID
* @param {String} newFileName 新しいファイル名
* @return {Object} コピー後のファイル情報
*/
function copyFile(auth, sourceFileId, destinationFolderId, newFileName) {
const url = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(sourceFileId)}/copy`;
const body = {};
if (destinationFolderId !== '') {
body.parents = [destinationFolderId];
}
if (newFileName !== '') {
body.name = newFileName;
}
const response = httpClient.begin()
.oauth2JwtBearer(auth, () => getAccessToken(auth))
.queryParam('fields', COPY_FIELDS)
.queryParam('supportsAllDrives', 'true')
.body(JSON.stringify(body), 'application/json; charset=UTF-8')
.post(url);
const status = response.getStatusCode();
const responseBody = response.getResponseAsString();
if (status >= 300) {
engine.log(responseBody);
throw `Failed to copy. Source ID: ${sourceFileId}, status: ${status}`;
}
let json;
try {
json = JSON.parse(responseBody);
} catch (error) {
throw `Failed to parse response. Source ID: ${sourceFileId}`;
}
if (json.id === undefined) {
throw `Failed to retrieve new file information. Source ID: ${sourceFileId}`;
}
return json;
}
const URL_TOKEN_REQUEST = 'https://oauth2.googleapis.com/token';
const SCOPE = 'https://www.googleapis.com/auth/drive';
/**
* @param {HttpAuthSettingWrapper} auth HTTP 認証設定
* @returns {{access_token: string}} アクセストークンを含むオブジェクト
*/
function getAccessToken(auth) {
const privateKeyId = auth.getPrivateKeyId();
const privateKey = auth.getPrivateKey();
const serviceAccount = auth.getCustomSecret1();
const scope = auth.getScope();
if (!scope.split(' ').includes(SCOPE)) {
throw new Error(`Scope ${SCOPE} must be included in the scope.`);
}
if (privateKeyId === '') {
throw new Error('Private Key ID is required.');
}
if (privateKey === '') {
throw new Error('Private Key is required.');
}
if (serviceAccount === '') {
throw new Error('Service Account must be set to Custom Secret 1.');
}
const header = {
alg: 'RS256',
typ: 'at+jwt',
kid: privateKeyId,
};
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: serviceAccount,
aud: URL_TOKEN_REQUEST,
sub: '',
iat: now,
exp: now + 3600,
scope,
};
const keyB = rsa.readKeyFromPkcs8(privateKey);
const assertion = jwt.build(header, payload, keyB);
const response = httpClient.begin()
.formParam('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer')
.formParam('assertion', assertion)
.post(URL_TOKEN_REQUEST);
const responseText = response.getResponseAsString();
if (response.getStatusCode() !== 200) {
engine.log(responseText);
throw new Error(`Failed to get Access token. status: ${response.getStatusCode()}`);
}
const result = JSON.parse(responseText);
if (result.access_token === undefined) {
engine.log(responseText);
throw new Error('Failed to get Access token. access token not found.');
}
return result;
}



