Recently, the AdWords Scripts team enabled the ability to apply labels to each account from the MccApp object. With this, we gain the ability to write a much cleaner version of the script that uses labels to indicate when each account has been processed. Using this method and running this script every hour, you could process up to 1,200 accounts per day.
The following code is meant to provide a framework for you to substitute your own MccApp code into. You can then schedule this code to run every hour, and it will continue processing the accounts in your MCC until each one of them is finished.
It will also attempt to notify you when there are accounts that return an error so that you can investigate. The one caveat about this script is that if you run into timeout limits on the Mcc level, namely in the results function, you might not apply the label to each successfully completed account. You can solve this by replacing line 76 with the following and removing line 89:
applyLabelsToCompletedAccounts([result.getCustomerId()]);
Anyway, I hope this helps and let me know if you run into any issues in the comments.
Thanks,
Russ
/****************************************** * MccApp Generic Runner Framework for any number of acounts * Version 1.1 * Changelog v1.1 - fixed issue with selector in yesterdays label function * Created By: Russ Savage (@russellsavage) * FreeAdWordsScripts.com ******************************************/ // The name of the script you are running // Used in error email subject line and label name var SCRIPT_NAME = 'Generic MCC App'; // Since timezones are not available at the MCC level // you need to set it here. You can use the local timezone // of each account in the function processing each account var TIMEZONE = 'PST'; // The date for today based on the timezone set above var TODAY_STR = Utilities.formatDate(new Date(), TIMEZONE, 'yyyy-MM-dd'); // The label prefix which is used to figure out today's label and // yesterday's label var LABEL_PREFIX = SCRIPT_NAME + ' - Completed - '; // This is the label that will be applied to each account // when it is successfully processed var FINISHED_LABEL_NAME = LABEL_PREFIX + TODAY_STR; // This is a list of email addresses to notify when // one of the accounts returns an error and is not processed. var NOTIFY = ['your_email@your_domain.com']; function main() { // Warning: if running in preview mode, this function will fail // and the selector that follows will also fail with "cannot read from AdWords" createLabelIfNeeded(); removeYesterdaysLabel(); // This might not exist, but try to remove it // Find all the accounts that have not been processed var accountIter = MccApp.accounts() .withCondition("LabelNames DOES_NOT_CONTAIN '"+FINISHED_LABEL_NAME+"'") .withLimit(50) .get(); // Add them to a list for the executeInParallel later var accountList = []; while(accountIter.hasNext()) { var account = accountIter.next(); accountList.push(account.getCustomerId()); } // If there are unprocessed accounts, process them if(accountList.length > 0) { MccApp.accounts() .withIds(accountList) .executeInParallel('doSomethingInEachAccount', 'reportOnResults'); } } // This function is called from executeInParallel and contains the // business logic for each account. Right now, it just has some // dummy logic to illustrate how this works. function doSomethingInEachAccount() { /************** * Replace this function with what * you want to do on each account **************/ Logger.log("In account: "+AdWordsApp.currentAccount().getName()+ " "+AdWordsApp.currentAccount().getCustomerId()); // This function must return a string so we use JSON.stringify() to // turn almost any object into a string quickly. return JSON.stringify({something:'else'}); } // This function will be called as soon as the function above // has been run on each account. The results object is an array // of the results returned by the function run in each account. function reportOnResults(results) { var completedAccounts = []; var erroredAccounts = []; for(var i in results) { var result = results[i]; // If the account function returns success if(result.getStatus() == 'OK') { // Add it to the list to apply the label to completedAccounts.push(result.getCustomerId()); /********************** * Fill in the code to process the results from * each account just below this. **********************/ var returnedValue = JSON.parse(result.getReturnValue()); } else { // In case of an error, we should notify someone so they can // check it out. erroredAccounts.push({customerId:result.getCustomerId(), error: result.getError()}); } } // Finally we apply the labels to each account applyLabelsToCompletedAccounts(completedAccounts); // And send an email with any errors notifyOfAccountsWithErrors(erroredAccounts); } /******************************* * Do not edit code below unless you know * what you are doing. *******************************/ // This function creates the required label to apply // to completed accounts. You can change the label name // by editing the FINISHED_LABEL_NAME variable at the top // of this script. function createLabelIfNeeded() { try { var labelIter = MccApp.accountLabels() .withCondition("LabelNames CONTAINS '"+FINISHED_LABEL_NAME+"'") .get(); } catch(e) { MccApp.createAccountLabel(FINISHED_LABEL_NAME); } } // This function applies FINISHED_LABEL_NAME to each completed account function applyLabelsToCompletedAccounts(completedAccounts) { var finishedAccountsIter = MccApp.accounts().withIds(completedAccounts).get(); while(finishedAccountsIter.hasNext()) { var account = finishedAccountsIter.next(); account.applyLabel(FINISHED_LABEL_NAME); } } // This function attempts to remove yesterday's label if it exists. // If it doesn't exist, it does nothing. function removeYesterdaysLabel() { var yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1); var yesterdayStr = Utilities.formatDate(yesterday, TIMEZONE, 'yyyy-MM-dd'); var yesterdayLabel = LABEL_PREFIX + yesterdayStr; Logger.log("Attempting to remove label: "+yesterdayLabel); try { var labelIter = MccApp.accountLabels().withCondition("Name CONTAINS '"+yesterdayLabel+"'").get(); while(labelIter.hasNext()) { labelIter.next().remove(); } } catch(e) { // do nothing } } // This function will send an email to each email in the // NOTIFY list from the top of the script with the specific error function notifyOfAccountsWithErrors(erroredAccounts) { if(!erroredAccounts || erroredAccounts.length == 0) { return; } if(typeof NOTIFY == 'undefined') { throw 'NOTIFY is not defined.'; } var subject = SCRIPT_NAME+' - Accounts with Errors - '+TODAY_STR; var htmlBody = 'The following Accounts had errors on the last run.<br / >'; htmlBody += 'Log in to AdWords: http://goo.gl/7mS6A'; var body = htmlBody; htmlBody += '<br / ><br / >'; htmlBody += '<table border="1" width="95%" style="border-collapse:collapse;">' + '<tr><td>Account Id</td><td>Error</td></tr>'; for(var i in erroredAccounts) { htmlBody += '<tr><td>'+ erroredAccounts[i].customerId + '</td><td>' + erroredAccounts[i].error + '</td></tr>'; } htmlBody += '</table>'; // Remove this line to get rid of the link back to this site. htmlBody += '<br / ><br / ><a href = "http://www.freeadwordsscripts.com" >FreeAdWordsScripts.com</a>'; var options = { htmlBody : htmlBody }; for(var i in NOTIFY) { Logger.log('Sending email to: '+NOTIFY[i]+' with subject: '+subject); MailApp.sendEmail(NOTIFY[i], subject, body, options); } }