Last active
January 9, 2019 04:34
-
-
Save rickt/199ca2be87522496e83de77bd5cd7db2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
// https://rickt.org/2019/01/08/playing-with-g-suite-mdm-mobile-device-data-using-go/ | |
import ( | |
"context" | |
"errors" | |
"flag" | |
"fmt" | |
"github.com/dustin/go-humanize" | |
"golang.org/x/oauth2/google" | |
"google.golang.org/api/admin/directory/v1" | |
"io/ioutil" | |
"log" | |
"os" | |
"strings" | |
"time" | |
) | |
// runtime usage: | |
// Usage of ./mdmtool: | |
// -all | |
// List all MDM mobile devices | |
// -imei string | |
// IMEI of mobile device to search fors | |
// -name string | |
// Name of mobile device owner to search for | |
// -sn string | |
// Serial number of mobile device to search for | |
// -status string | |
// Search for mobile devices with specific status | |
var ( | |
adminuser = os.Getenv("GSUITE_ADMINUSER") | |
companyid = os.Getenv("GSUITE_COMPANYID") | |
credsfile = os.Getenv("SVC_ACCOUNT_CREDS_JSON") | |
devices *admin.MobileDevices | |
row int = 0 | |
scopes = "https://www.googleapis.com/auth/admin.directory.device.mobile.readonly" | |
searchtype = "all" // default search type | |
sortorder = "name" // default sort order | |
) | |
// flags | |
var ( | |
all *bool = flag.Bool("all", false, "List all MDM mobile devices") | |
imei *string = flag.String("imei", "", "IMEI of mobile device to search for") | |
name *string = flag.String("name", "", "Name of mobile device owner to search for") | |
sn *string = flag.String("sn", "", "Serial number of mobile device to search for") | |
status *string = flag.String("status", "", "Search for mobile devices with specific status") | |
) | |
// helper func to check errors | |
func checkError(err error) { | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
// check the command line arguments/flags | |
func checkFlags() (string, error) { | |
// parse the flags | |
flag.Parse() | |
// show all devices? | |
if *all == true { | |
// -all shows ALL devices so doesn't work with any other option | |
if *name != "" || *imei != "" || *sn != "" || *status != "" { | |
return "", errors.New("Error: -all cannot be used with any other option") | |
} | |
return "all", nil | |
} | |
// name search | |
if *name != "" { | |
// don't use -name and any other search option | |
if *imei != "" || *sn != "" || *status != "" { | |
return "", errors.New("Error: cannot use -name and any other search options") | |
} | |
return "name", nil | |
} | |
// IMEI search | |
if *imei != "" { | |
// don't use -imei and any other search option | |
if *name != "" || *sn != "" || *status != "" { | |
return "", errors.New("Error: cannot use -imei and any other search options") | |
} | |
return "imei", nil | |
} | |
// Serial number search | |
if *sn != "" { | |
// don't use -sn and any other search option | |
if *name != "" || *imei != "" || *status != "" { | |
return "", errors.New("Error: cannot use -sn and any other search options") | |
} | |
return "sn", nil | |
} | |
// Status search | |
if *status != "" { | |
// don't use -status and any other search option | |
if *name != "" || *imei != "" || *sn != "" { | |
return "", errors.New("Error: cannot use -status and any other search options") | |
} | |
return "status", nil | |
} | |
// invalid search | |
if *all == false && *name == "" && *imei == "" && *sn == "" && *status == "" { | |
flag.PrintDefaults() | |
return "", errors.New("Error: no search options specified") | |
} | |
return "", nil | |
} | |
// helper function to do a case-insensitive search | |
func ciContains(a, b string) bool { | |
return strings.Contains(strings.ToUpper(a), strings.ToUpper(b)) | |
} | |
func main() { | |
// check the flags to determine type of search | |
searchtype, err := checkFlags() | |
checkError(err) | |
// read in the service account's JSON credentials file | |
creds, err := ioutil.ReadFile(credsfile) | |
checkError(err) | |
// create JWT config from the service account's JSON credentials file | |
jwtcfg, err := google.JWTConfigFromJSON(creds, scopes) | |
checkError(err) | |
// specify which admin user the API calls should "run as" | |
jwtcfg.Subject = adminuser | |
// make the client using our JWT config | |
gc, err := admin.New(jwtcfg.Client(context.Background())) | |
checkError(err) | |
// get the data | |
devices, err = gc.Mobiledevices.List(companyid).OrderBy(sortorder).Do() | |
checkError(err) | |
// iterate through the slice of devices | |
for _, device := range devices.Mobiledevices { | |
// what type of search are we doing? | |
switch searchtype { | |
// show all mobile devices | |
case "all": | |
row++ | |
printDeviceData(device) | |
// name search: iterate through the slice of names associated with the device | |
case "name": | |
for _, username := range device.Name { | |
// look for the specific user | |
if ciContains(username, *name) { | |
row++ | |
printDeviceData(device) | |
} | |
} | |
// IMEI search: look for a specific IMEI | |
case "imei": | |
// remove all spaces from IMEI then search for specific IMEI | |
// IMEI can be misreported via MDM with spaces, so remove them | |
if strings.Replace(device.Imei, " ", "", -1) == strings.Replace(*imei, " ", "", -1) { | |
row++ | |
printDeviceData(device) | |
break | |
} | |
// serial number search: look for a specific serial number | |
// SN can be misreported via MDM with spaces, so remove them | |
case "sn": | |
if strings.Replace(device.SerialNumber, " ", "", -1) == strings.Replace(*sn, " ", "", -1) { | |
row++ | |
printDeviceData(device) | |
break | |
} | |
// Status search | |
case "status": | |
if ciContains(device.Status, *status) { | |
row++ | |
printDeviceData(device) | |
} | |
} | |
} | |
// if 0 rows returned, exit | |
if row == 0 { | |
log.Fatal("No mobile devices match specified search criteria") | |
} else { | |
// print the final/closing line of dashes | |
printLine() | |
} | |
fmt.Printf("%d row(s) of mobile device data returned.\n", row) | |
} | |
// func to print out device data | |
func printDeviceData(device *admin.MobileDevice) { | |
// print header only on first row of data | |
if row == 1 { | |
printHeader() | |
} | |
// convert last sync string to time.Time so we can humanize the last sync timestamp | |
t, err := time.Parse(time.RFC3339, device.LastSync) | |
checkError(err) | |
fmt.Printf("%-16.16s | %-14.14s | %-16.16s | %-18.18s | %-13.13s | %-18.18s | %-20.20s\n", device.Model, device.Os, strings.Replace(device.SerialNumber, " ", "", -1), strings.Replace(device.Imei, " ", "", -1), device.Status, humanize.Time(t), device.Name[0]) | |
return | |
} | |
// func to print a line | |
func printLine() { | |
// print a line | |
fmt.Printf("-----------------+----------------+------------------+--------------------+---------------+--------------------+---------------\n") | |
} | |
// func to print the header | |
func printHeader() { | |
// print the first line of dashes | |
printLine() | |
// print header line | |
fmt.Printf("Model | OS & Version | Serial # | IMEI | Status | Last Sync | Owner\n") | |
// print a line of dashes under the header line | |
printLine() | |
} | |
// EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment