Skip to content

More WP-CLI Multisite Tricks

Comment Management

On your multisite installation, some blog admins might not be keeping their comment stream up to date. Why clutter your database with the ones that are left in limbo between approved, spam and deleted? Run this commmand:
[cc lang=”bash”]
wp site list –field=url | xargs -t -n1 -I % wp comment list –status=hold –url=% \
–skip-themes –skip-plugins –user=super_admin >>/home/youraccount/pluginoutput.txt 2>&1

You could change their status to “spam” or something, but to be nice you can look through them and make a decision on each.

Obsolete Blogs

At the job, students come and go every four years and some of their freshman assignments sort of lose their usefulness after a time. This script goes through all of the blogs, checks for exceptions (faculty, templates, the home blog etc.) then either deletes or archives, depending on the timestamp for creation and last-modified.
[cc lang=”PHP”]
$details ) {
$fields = preg_split(‘/\s+/’,$details);
if ( in_array($fields[1], $keepers)) {
/* These are the ones to delete */
elseif ( $fields[2] < ‘2013-07-14’ && $fields[4] < ‘2016-07-14’ ) {
echo “Delete: $blog ID: ” . $fields[0] . “URL: ” . $fields[1] . “\n”;
echo “Registered: ” . $fields[2] . ‘, Updated: ‘ . $fields[4] . “\n”;
$res = exec(‘wp site delete ‘ . $fields[0] . ‘ –yes’);
echo “$res\n\n”;
/* archive these */
elseif ( $fields[2] < ‘2017-06-01’ ) {
echo “Archive: $blog ID: ” . $fields[0] . ” URL: ” . $fields[1] . “\n”;
echo “Registered: ” . $fields[2] . ‘, Updated: ‘ . $fields[4] . “\n\n”;
$result .= exec(‘wp site archive ‘ . $fields[0]);
echo “$result \n”;

After establishing the list of blogs to keep, we get the site list, capturing the ID, URL, date registered and date last updated. We screen out ones that are already archived or deleted, but you may want to handle this differently. Note that when you archive a blog, that changes its last-modified date — something that actually could come in handy because later your test could be whether the date equals the last time you ran the script. Skipping themes and plugins is something I always do, because for these purposes it really doesn’t matter to me if a theme or plugin has something that throws an error.

Splitting each line of output gives you an array of
[0] = the blog’s numeric ID;
[1] = the blog’s URL;
[2] = the calendar date of registration;
[3] = the time of registration (which we don’t use here);
[4] = the calendar date of the last update;
[5] = the time of the last update (again, not relevant to this use).

[cci lang=”vim”]wp site delete requires[/cci] [0], and won’t accept the URL. I output it here just because it means I don’t have to look something up later. Also note that [cci lang=”vim”]wp site delete[/cci] is going to need the “–yes” flag, otherwise your script will mysteriously hang overnight waiting for the “yes” from the terminal which will never come.

If we haven’t already deleted it, we’ll archive the blog if it’s older than June 1, 2017. For this we don’t care when it was modified, though you might.

Obviously your choices for all the dates will vary. You could check to see whether the creation date and time is exactly the same as the last-modified date and time, meaning it was a blog set up and immediately abandoned. You could take those blogs and make sure they were at least two (or three, or four) years old.


I’m not sure Python buys you anything. I don’t really know the language, but here are some bits I got working.

List of blog IDs and URLs

[cc lang=”Python”]
>>> import subprocess
>>> import os
>>> os.chdir(“/path/to/wordpress/root”) “””gets you to the web server root
>>>[“wp site list –fields=blog_id,url –skip-themes –skip-plugins –user=ad_tltc”], shell=True) “””list of site IDs and URs

Alternate way

This gives you the same, but as one string stored in “bloglist” (tab- and newline-delimited).
[cc lang=”Python”]
>>> import subprocess
>>> from subprocess import Popen, PIPE
>>> bloglist = subprocess.Popen([“wp”,”site”,”list”,”–fields=blog_id,url”],stdout=subprocess.PIPE).communicate()[0] “””gives only the necessary fields!

It’s worth noting that in Python when you’ve got a multi-part command you’re passing to Popen, each part of it is a separate string in the array passed to Popen, including the list of flags.


Maybe it’s time to reawaken my dormant love affair with Perl. Its syntax for input and output, splitting strings, managing arrays and variables, is more compact all around. So for example, to get the blog output from above you only need:
[cc lang=”Perl”]
use strict;
my $path = ‘/path/to/wordpress/root’;
my $bloglist = qx{wp site list –path=$path –fields=blog_id,url};


Bulk Adding Users

For some classroom assignments, faculty will set up some kind of site, which they’d like an entire class or cohort to work on. In some cases the students are already in the system; in other cases, they’re not. As an additional complication, the newly created ones need to have a flag set so that our Directory Authentication system will recognize them.

Here’s a fully functioning script that handles that, with the names disguised to protect the innocent.
[cc lang=”Perl”]
use strict;
my $path = ‘/path/to/wordpress/root’;
my $undergradblog = ‘’;
my @undergrad = (
[‘logon1’, ‘Kathrine’, ‘Doe’, ‘Kathrine Doe’, ‘’],
[‘logon2’, ‘Anthony’, ‘Doe’, ‘Anthony N Doe’, ‘’],
[‘logon3’, ‘Emma’, ‘Roe’, ‘Emma T Roe’, ’’]

foreach my $user (0 .. @undergrad-1) {
print “User $undergrad[$user][0]\n”;
my $tmp = $undergrad[$user];
addUser( \@$tmp , $path);
my $addedToBlog = qx{wp user set-role $undergrad[$user][0] \
editor –url=$undergradblog –path=$path};
print “$addedToBlog\n”;

sub addUser {
my ($userinfo, $path) = @_;
#check if it exists
my $userexists = qx{wp user get $userinfo->[0] –path=$path –field=email};
print “$userexists\n”;
if ( ! $userexists ) {
# $userexists will be null if the command fails,
# don’t be fooled by what’s output to the console.
#Time to create a new one
my $newuserid;
my $newusercreated = qx{wp user create $userinfo->[0] $userinfo->[4] \
–role=subscriber –path=$path};
print “$newusercreated\n”;
if ( $newusercreated =~ /^Success: Created user/ ) {
#your own custom attribute. We’ll set Directory Authentication
my $dirAuthFlagSet = qx{wp user meta add $userinfo->[0] wpDirAuthFlag 1 \
print “$dirAuthFlagSet\n”;
#and add metadata for their first, last and display names
my $userFirstname = qx{wp user meta update $userinfo->[0] \
first_name $userinfo->[1] –path=$path};
print “$userFirstname\n”;
my $userLastname = qx{wp user meta update $userinfo->[0] \
last_name $userinfo->[2] –path=$path};
print “$userLastname\n”;
my $userDisplayname = qx{wp user update $userinfo->[0] \
–display_name=’$userinfo->[3]’ –path=$path};
#don’t forget the quotation marks here!
print “$userDisplayname\n”;


Bulk Blog Creation

Periodically I’m called on to to mass-duplication of blogs. This is usually for a group of students numbering from the tens to the hundreds who will start working with a fully configured blog with plugins, themes, settings and users already in place. Our next installment will take a look at that.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Share This