Compare commits

..

14 Commits
v3.3 ... v3.31

Author SHA1 Message Date
Jay Lee
00a61dae70 for include passlib.handlers.sha2_crypt 2014-07-09 10:31:57 -04:00
Jay Lee
fef5ec622e force include passlib.handlers.sha2_crypt 2014-07-09 10:31:12 -04:00
Jay Lee
c2f5f6275c limit cache files to 64 chars 2014-07-09 08:37:44 -04:00
Jay Lee
71bb053276 don't try to download Drive folders, fixes to delete all aliases 2014-07-08 08:07:28 -04:00
Jay Lee
303eb1b240 fix delete photo command trying to print non-existant filename 2014-07-08 08:05:54 -04:00
Jay Lee
fc06cc89c5 for gam info user, make sure fields exist before printing 2014-07-05 20:04:01 -04:00
Jay Lee
21bfa5c84e fix whatis and nicer count for all users command 2014-07-05 14:56:27 -04:00
Jay Lee
5a10b64593 info user cleanup and rename to vfe 2014-07-05 14:10:47 -04:00
Jay Lee
47da88c3bb add "gam user <email> delete aliases" command and "gam update user <email> email vfe" command 2014-07-05 12:43:44 -04:00
Jay Lee
4656e0857f gam report logins works, few small fixes 2014-07-05 11:35:40 -04:00
Jay Lee
10d6a0d454 7-zip is 32-bit and stop building python source package 2014-07-04 14:35:18 -04:00
Jay Lee
2c5cf457f9 no upload to google code 2014-07-04 14:33:11 -04:00
Jay Lee
5787b13fa5 admin-settings-v1.json, not adminsettings-v1.json. 2014-07-04 14:32:16 -04:00
Jay Lee
9da83b7074 copy LICENSE instead of LICENSE.txt 2014-07-04 14:17:26 -04:00
5 changed files with 164 additions and 147 deletions

View File

@@ -8,45 +8,16 @@ del /q /f gam-%1-windows.zip
del /q /f gam-%1-windows-x64.zip
\python27-32\python.exe setup.py py2exe
xcopy LICENSE.txt gam\
xcopy LICENSE gam\
xcopy whatsnew.txt gam\
xcopy cacert.pem gam\
xcopy adminsettings-v1.json gam\
xcopy admin-settings-v1.json gam\
del gam\w9xpopen.exe
"%ProgramFiles%\7-Zip\7z.exe" a -tzip gam-%1-windows.zip gam\ -xr!.svn
"%ProgramFiles(x86)%\7-Zip\7z.exe" a -tzip gam-%1-windows.zip gam\ -xr!.svn
\python27\python.exe setup-64.py py2exe
xcopy LICENSE.txt gam-64\
xcopy LICENSE gam-64\
xcopy whatsnew.txt gam-64\
xcopy cacert.pem gam-64\
xcopy adminsettings-v1.json gam-64\
"%ProgramFiles%\7-Zip\7z.exe" a -tzip gam-%1-windows-x64.zip gam-64\ -xr!.svn
mkdir python-src-%1
mkdir python-src-%1\gdata
mkdir python-src-%1\atom
mkdir python-src-%1\apiclient
mkdir python-src-%1\httplib2
mkdir python-src-%1\oauth2client
mkdir python-src-%1\simplejson
mkdir python-src-%1\uritemplate
xcopy gam.py python-src-%1\
xcopy LICENSE.txt python-src-%1\
xcopy whatsnew.txt python-src-%1\
xcopy /e gdata\*.* python-src-%1\gdata
xcopy /e atom\*.* python-src-%1\atom
xcopy /e apiclient\*.* python-src-%1\apiclient
xcopy /e httplib2\*.* python-src-%1\httplib2
xcopy /e oauth2client\*.* python-src-%1\oauth2client
xcopy /e simplejson\*.* python-src-%1\simplejson
xcopy /e uritemplate\*.* python-src-%1\uritemplate
xcopy cacert.pem python-src-%1\
xcopy adminsettings-v1.json python-src-%1\
cd python-src-%1
"%ProgramFiles%\7-Zip\7z.exe" a -tzip ..\gam-%1-python-src.zip * -xr!.svn
cd ..
\python27\python.exe googlecode_upload.py --project google-apps-manager --summary "GAM %1 Windows" --user %2 --password %3 --labels "ALPHA,Type-Archive,OpSys-Windows" gam-%1-windows.zip
\python27\python.exe googlecode_upload.py --project google-apps-manager --summary "GAM %1 Windows x64" --user %2 --password %3 --labels "ALPHA,Type-Archive,OpSys-Windows" gam-%1-windows-x64.zip
\python27\python.exe googlecode_upload.py --project google-apps-manager --summary "GAM %1 Python Source" --user %2 --password %3 --labels "ALPHA,Type-Archive,OpSys-All" gam-%1-python-src.zip
xcopy admin-settings-v1.json gam-64\
"%ProgramFiles(x86)%\7-Zip\7z.exe" a -tzip gam-%1-windows-x64.zip gam-64\ -xr!.svn

256
gam.py
View File

@@ -24,7 +24,7 @@ For more information, see http://code.google.com/p/google-apps-manager
"""
__author__ = u'Jay Lee <jay0lee@gmail.com>'
__version__ = u'3.3'
__version__ = u'3.31'
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
import sys, os, time, datetime, random, socket, csv, platform, re, calendar, base64, hashlib
@@ -201,7 +201,7 @@ def doGAMCheckForUpdates():
def commonAppsObjInit(appsObj):
#Identify GAM to Google's Servers
appsObj.source = u'Dito GAM %s / %s / Python %s.%s.%s %s / %s %s /' % (__version__, __author__,
appsObj.source = u'Dito GAM %s - http://git.io/gam / %s / Python %s.%s.%s %s / %s %s /' % (__version__, __author__,
sys.version_info[0], sys.version_info[1], sys.version_info[2], sys.version_info[3],
platform.platform(), platform.machine())
#Show debugging output if debug.gam exists
@@ -426,6 +426,10 @@ def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_re
print u'Error: %s' % e
sys.exit(4)
def restart_line():
sys.stderr.write('\r')
sys.stderr.flush()
def callGAPIpages(service, function, items=u'items', nextPageToken=u'nextPageToken', page_message=None, message_attribute=None, **kwargs):
pageToken = None
all_pages = list()
@@ -456,6 +460,7 @@ def callGAPIpages(service, function, items=u'items', nextPageToken=u'nextPageTok
except KeyError:
show_message = show_message.replace(u'%%first_item%%', '')
show_message = show_message.replace(u'%%last_item%%', '')
restart_line()
sys.stderr.write(show_message)
try:
all_pages += this_page[items]
@@ -819,33 +824,36 @@ def showReport():
for app in auth_apps: # put apps at bottom
cust_attributes.append(app)
output_csv(csv_list=cust_attributes, titles=titles, list_type=u'Customer Report - %s' % try_date, todrive=to_drive)
elif report in [u'doc', u'docs', u'login', u'admin', u'drive']:
elif report in [u'doc', u'docs', u'login', u'logins', u'admin', u'drive']:
if report == u'doc':
report = u'docs'
elif report == u'logins':
report = u'login'
page_message = u'Got %%num_items%% items\n'
activities = callGAPIpages(service=rep.activities(), function=u'list', page_message=page_message, applicationName=report, userKey=userKey, customerId=customerId, actorIpAddress=actorIpAddress, startTime=startTime, endTime=endTime, eventName=eventName, filters=filters)
attrs = []
titles = []
for activity in activities:
events = activity[u'events']
del activity[u'events']
activity_row = flatten_json(activity)
for event in events:
row = flatten_json(event)
row.update(activity_row)
for item in row.keys():
if item not in titles:
titles.append(item)
attrs.append(row)
header = {}
titles.remove(u'name')
titles = sorted(titles)
titles.insert(0, u'name')
for title in titles:
header[title] = title
attrs.insert(0, header)
cap_report = u'%s%s' % (report[0].upper(), report[1:])
output_csv(attrs, titles, u'%s Activity Report' % cap_report, to_drive)
if len(activities) > 0:
attrs = []
titles = []
for activity in activities:
events = activity[u'events']
del activity[u'events']
activity_row = flatten_json(activity)
for event in events:
row = flatten_json(event)
row.update(activity_row)
for item in row.keys():
if item not in titles:
titles.append(item)
attrs.append(row)
header = {}
titles.remove(u'name')
titles = sorted(titles)
titles.insert(0, u'name')
for title in titles:
header[title] = title
attrs.insert(0, header)
cap_report = u'%s%s' % (report[0].upper(), report[1:])
output_csv(attrs, titles, u'%s Activity Report' % cap_report, to_drive)
def doDelegates(users):
emailsettings = getEmailSettingsObject()
@@ -1544,7 +1552,7 @@ def deletePhoto(users):
user = user[4:]
elif user.find('@') == -1:
user = u'%s@%s' % (user, domain)
print u"Deleting photo for %s to %s (%s of %s)" % (user, filename, i, count)
print u"Deleting photo for %s (%s of %s)" % (user, i, count)
callGAPI(service=cd.users().photos(), function='delete', userKey=user)
i += 1
@@ -2234,6 +2242,9 @@ def downloadDriveFile(users):
for fileId in fileIds:
extension = None
result = callGAPI(service=drive.files(), function=u'get', fileId=fileId, fields=u'fileSize,title,mimeType,downloadUrl,exportLinks')
if result[u'mimeType'] == u'application/vnd.google-apps.folder':
print u'Skipping download of folder %s' % result[u'title']
continue
try:
result[u'fileSize'] = int(result[u'fileSize'])
if result[u'fileSize'] < 1024:
@@ -3844,6 +3855,20 @@ def doUpdateUser(users):
except KeyError:
body[u'relations'] = [relation,]
i += 1
elif sys.argv[i].lower() == u'otheremail':
do_update_user = True
an_email = dict()
i += 1
an_email[u'type'] = sys.argv[i]
if an_email[u'type'].lower() not in [u'custom', u'home', u'other', u'work']:
an_email[u'type'] = u'custom'
an_email[u'customType'] = sys.argv[i]
i += 1
an_email[u'address'] = sys.argv[i]
if u'emails' not in body:
body[u'emails'] = list()
body[u'emails'].append(an_email)
i += 1
elif sys.argv[i].lower() == u'externalid':
do_update_user = True
externalid = dict()
@@ -3872,12 +3897,32 @@ def doUpdateUser(users):
user = user[4:]
elif user.find(u'@') == -1:
user = u'%s@%s' % (user, domain)
if u'primaryEmail' in body and body[u'primaryEmail'][:4].lower() == u'vfe@':
user_primary = callGAPI(service=cd.users(), function=u'get', userKey=user, fields=u'primaryEmail,id')
user = user_primary[u'id']
user_primary = user_primary[u'primaryEmail']
user_name = user_primary[:user_primary.find(u'@')]
user_domain = user_primary[user_primary.find(u'@')+1:]
body[u'primaryEmail'] = u'vfe.%s.%05d@%s' % (user_name, random.randint(1,99999), user_domain)
body[u'emails'] = [{u'type': u'custom', u'customType': u'former_employee', u'primary': False, u'address': user_primary}]
sys.stderr.write(u'updating user %s...\n' % user)
if do_update_user:
result = callGAPI(service=cd.users(), function=u'patch', soft_errors=True, userKey=user, body=body)
if do_admin_user:
result2 = callGAPI(service=cd.users(), function=u'makeAdmin', soft_errors=True, userKey=user, body={u'status': is_admin})
def doRemoveUsersAliases(users):
cd = buildGAPIObject(u'directory')
for user in users:
user_aliases = callGAPI(service=cd.users(), function=u'get', userKey=user, fields=u'aliases,id,primaryEmail')
user_id = user_aliases[u'id']
user_primary = user_aliases[u'primaryEmail']
print u'%s has %s aliases' % (user_primary, len(user_aliases[u'aliases']))
if u'aliases' in user_aliases:
for an_alias in user_aliases[u'aliases']:
print u' removing alias %s for %s...' % (an_alias, user_aliases[u'primaryEmail'])
callGAPI(service=cd.users().aliases(), function=u'delete', userKey=user_aliases[u'id'], alias=an_alias)
def doRemoveUsersGroups(users):
cd = buildGAPIObject(u'directory')
for user in users:
@@ -4227,7 +4272,7 @@ def doWhatIs():
if email.find(u'@') == -1:
email = u'%s@%s' % (email, domain)
try:
user_or_alias = callGAPI(service=cd.users(), function=u'get', throw_reasons=[u'badRequest', u'invalid'], userKey=email, fields=u'primaryEmail')
user_or_alias = callGAPI(service=cd.users(), function=u'get', throw_reasons=[u'notFound', u'badRequest', u'invalid'], userKey=email, fields=u'primaryEmail')
if user_or_alias[u'primaryEmail'].lower() == email.lower():
sys.stderr.write(u'%s is a user\n\n' % email)
doGetUserInfo(user_email=email)
@@ -4240,10 +4285,10 @@ def doWhatIs():
sys.stderr.write(u'%s is not a user...\n' % email)
sys.stderr.write(u'%s is not a user alias...\n' % email)
try:
group = callGAPI(service=cd.groups(), function=u'get', throw_reasons=u'badRequest', groupKey=email, fields=u'email')
group = callGAPI(service=cd.groups(), function=u'get', throw_reasons=[u'notFound', u'badRequest'], groupKey=email, fields=u'email')
except apiclient.errors.HttpError:
sys.stderr.write(u'%s is not a group either!\n\nDoesn\'t seem to exist!' % email)
sys.exit(0)
sys.stderr.write(u'%s is not a group either!\n\nDoesn\'t seem to exist!\n\n' % email)
sys.exit(1)
if group[u'email'].lower() == email.lower():
sys.stderr.write(u'%s is a group\n\n' % email)
doGetGroupInfo(group_name=email)
@@ -4289,75 +4334,83 @@ def doGetUserInfo(user_email=None):
sys.exit(3)
user = callGAPI(service=cd.users(), function=u'get', userKey=user_email)
print u'User: %s' % user[u'primaryEmail']
try:
if u'name' in user and u'givenName' in user[u'name']:
print u'First Name: %s' % user[u'name'][u'givenName']
except KeyError:
print u'First Name: <blank>'
try:
if u'name' in user and u'familyName' in user[u'name']:
print u'Last Name: %s' % user[u'name'][u'familyName']
except KeyError:
print u'Last Name: <blank>'
print u'Is a Super Admin: %s' % user[u'isAdmin']
print u'Is Delegated Admin: %s' % user[u'isDelegatedAdmin']
print u'Has Agreed to Terms: %s' % user[u'agreedToTerms']
print u'IP Whitelisted: %s' % user[u'ipWhitelisted']
print u'Account Suspended: %s' % user[u'suspended']
try:
if u'isAdmin' in user:
print u'Is a Super Admin: %s' % user[u'isAdmin']
if u'isDelegatedAdmin' in user:
print u'Is Delegated Admin: %s' % user[u'isDelegatedAdmin']
if u'agreedToTerms' in user:
print u'Has Agreed to Terms: %s' % user[u'agreedToTerms']
if u'ipWhitelisted' in user:
print u'IP Whitelisted: %s' % user[u'ipWhitelisted']
if u'suspended' in user:
print u'Account Suspended: %s' % user[u'suspended']
if u'suspensionReason' in user:
print u'Suspension Reason: %s' % user[u'suspensionReason']
except KeyError:
pass
print u'Must Change Password: %s' % user[u'changePasswordAtNextLogin']
print u'Google Unique ID: %s' % user[u'id']
print u'Customer ID: %s' % user[u'customerId']
print u'Mailbox is setup: %s' % user[u'isMailboxSetup']
print u'Included in GAL: %s' % user[u'includeInGlobalAddressList']
print u'Creation Time: %s' % user[u'creationTime']
if user[u'lastLoginTime'] == u'1970-01-01T00:00:00.000Z':
print u'Last login time: Never'
else:
print u'Last login time: %s' % user[u'lastLoginTime']
try:
if u'changePasswordAtNextLogin' in user:
print u'Must Change Password: %s' % user[u'changePasswordAtNextLogin']
if u'id' in user:
print u'Google Unique ID: %s' % user[u'id']
if u'customerId' in user:
print u'Customer ID: %s' % user[u'customerId']
if u'isMailboxSetup' in user:
print u'Mailbox is setup: %s' % user[u'isMailboxSetup']
if u'includeInGlobalAddressList' in user:
print u'Included in GAL: %s' % user[u'includeInGlobalAddressList']
if u'creationTime' in user:
print u'Creation Time: %s' % user[u'creationTime']
if u'lastLoginTime' in user:
if user[u'lastLoginTime'] == u'1970-01-01T00:00:00.000Z':
print u'Last login time: Never'
else:
print u'Last login time: %s' % user[u'lastLoginTime']
if u'orgUnitPath' in user:
print u'Google Org Unit Path: %s\n' % user[u'orgUnitPath']
except KeyError:
print u'Google Org Unit Path: Unknown\n'
try:
if u'thumbnailPhotoUrl' in user:
print u'Photo URL: %s\n' % user[u'thumbnailPhotoUrl']
except KeyError:
pass
print u'IMs:'
try:
if u'ims' in user:
print u'IMs:'
for im in user[u'ims']:
for key in im.keys():
print u' %s: %s' % (key, im[key])
print u''
except KeyError:
pass
print u'Addresses:'
try:
if u'addresses' in user:
print u'Addresses:'
for address in user[u'addresses']:
for key in address.keys():
print u' %s: %s' % (key, address[key])
print ''
except KeyError:
pass
print u'Organizations:'
try:
if u'organizations' in user:
print u'Organizations:'
for org in user[u'organizations']:
for key in org.keys():
print u' %s: %s' % (key, org[key])
print u''
except KeyError:
pass
print u'Phones:'
try:
if u'phones' in user:
print u'Phones:'
for phone in user[u'phones']:
for key in phone.keys():
print u' %s: %s' % (key, phone[key])
print u''
except KeyError:
pass
print u'Relations:'
try:
if u'emails' in user:
if len(user[u'emails']) > 1:
print u'Other Emails:'
for an_email in user[u'emails']:
if an_email[u'address'].lower() == user[u'primaryEmail'].lower():
continue
for key in an_email.keys():
if key == u'type' and an_email[key] == u'custom':
continue
if key == u'customType':
print u' type: %s' % an_email[key]
else:
print u' %s: %s' % (key, an_email[key])
print u''
if u'relations' in user:
print u'Relations:'
for relation in user[u'relations']:
for key in relation.keys():
if key == u'type' and relation[key] == u'custom':
@@ -4367,10 +4420,8 @@ def doGetUserInfo(user_email=None):
else:
print u' %s: %s' % (key, relation[key])
print u''
except KeyError:
pass
print u'External IDs:'
try:
if u'externalIds' in user:
print u'External IDs:'
for id in user[u'externalIds']:
for key in id.keys():
if key == u'type' and id[key] == u'custom':
@@ -4380,31 +4431,22 @@ def doGetUserInfo(user_email=None):
else:
print u' %s: %s' % (key, id[key])
print u''
except KeyError:
pass
if getAliases:
print u'Email Aliases:'
try:
if u'aliases' in user:
print u'Email Aliases:'
for alias in user[u'aliases']:
print u' ' + alias
except KeyError:
pass
print u'Non-Editable Aliases:'
try:
print u' %s' % alias
if u'nonEditableAliases' in user:
print u'Non-Editable Aliases:'
for alias in user[u'nonEditableAliases']:
print u' ' + alias
except KeyError:
pass
print u' %s' % alias
if getGroups:
groups = callGAPI(service=cd.groups(), function=u'list', userKey=user_email)
print u'Groups:'
try:
if u'groups' in groups:
print u'Groups:'
for group in groups[u'groups']:
print u' %s <%s>' % (group[u'name'], group[u'email'])
except KeyError:
pass
if getLicenses:
print
print u'Licenses:'
lic = buildGAPIObject(api='licensing')
for sku in [u'Google-Apps', u'Google-Apps-For-Business', u'Google-Apps-Unlimited', u'Google-Apps-For-Postini',
@@ -5387,7 +5429,7 @@ def doPrintUsers():
user_fields.append(u'thumbnailPhotoUrl')
i += 1
elif sys.argv[i].lower() == u'id':
user_fields.append(u',id')
user_fields.append(u'id')
i += 1
elif sys.argv[i].lower() == u'creationtime':
user_fields.append(u'creationTime')
@@ -6409,7 +6451,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
page_message = None
if not silent:
sys.stderr.write(u"Getting %s of %s (may take some time for large groups)..." % (member_type_message, group))
page_message = u'Got %%%%num_items%%%% %s...\n' % member_type_message
page_message = u'Got %%%%total_items%%%% %s...' % member_type_message
members = callGAPIpages(service=cd.members(), function=u'list', page_message=page_message, groupKey=group, roles=member_type, fields=u'nextPageToken,members(email,id)')
users = []
for member in members:
@@ -6426,7 +6468,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
page_message = None
if not silent:
sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n")
page_message = u'Got %%num_items%% users.\n'
page_message = u'Got %%total_items%% users...'
members = callGAPIpages(service=cd.users(), function=u'list', items=u'users', page_message=page_message, customer=customerId, fields=u'nextPageToken,users(primaryEmail,id,orgUnitPath)', query=u"orgUnitPath='%s'" % ou, maxResults=500)
for member in members:
if ou.lower() != member[u'orgUnitPath'].lower():
@@ -6445,7 +6487,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
page_message = None
if not silent:
sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n")
page_message = u'Got %%num_items%% users\n'
page_message = u'Got %%total_items%% users..'
members = callGAPIpages(service=cd.users(), function=u'list', items=u'users', page_message=page_message, customer=customerId, fields=u'nextPageToken,users(primaryEmail,id)', query=u"orgUnitPath='%s'" % ou, maxResults=500)
for member in members:
if return_uids:
@@ -6457,7 +6499,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
got_uids = True
users = []
if not silent: sys.stderr.write(u"Getting all users that match query %s (may take some time on a large domain)...\n" % entity)
page_message = u'Got %%num_items%% users\n'
page_message = u'Got %%total_items%% users...'
members = callGAPIpages(service=cd.users(), function=u'list', items=u'users', page_message=page_message, customer=customerId, fields=u'nextPageToken,users(primaryEmail,id)', query=entity, maxResults=500)
for member in members:
if return_uids:
@@ -6478,8 +6520,8 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
got_uids = True
users = []
if entity == u'users':
if not silent: sys.stderr.write(u"Getting all users in Google Apps account (may take some time on a large account)...")
page_message = u'Got %%num_items%% users\n'
if not silent: sys.stderr.write(u"Getting all users in Google Apps account (may take some time on a large account)...\n")
page_message = u'Got %%total_items%% users...'
all_users = callGAPIpages(service=cd.users(), function=u'list', items=u'users', page_message=page_message, customer=customerId, fields=u'nextPageToken,users(primaryEmail,suspended,id)', maxResults=500)
for member in all_users:
if member[u'suspended'] == False:
@@ -7126,6 +7168,8 @@ try:
doDelTokens(users)
elif delWhat in [u'group', u'groups']:
doRemoveUsersGroups(users)
elif delWhat in [u'alias', u'aliases']:
doRemoveUsersAliases(users)
elif delWhat in [u'emptydrivefolders']:
deleteEmptyDriveFolders(users)
elif delWhat in [u'drivefile']:

View File

@@ -255,8 +255,8 @@ def safename(filename):
filename = re_slash.sub(",", filename)
# limit length of filename
if len(filename)>200:
filename=filename[:200]
if len(filename)>64:
filename=filename[:64]
return ",".join((filename, filemd5))
NORMALIZE_SPACE = re.compile(r'(?:\r\n)?[ \t]+')

View File

@@ -5,12 +5,13 @@ sys.argv.append('py2exe')
setup(
console = ['gam.py'],
zipfile = None,
options = {'py2exe':
{'optimize': 2,
'bundle_files': 3,
'includes': ['passlib.handlers.sha2_crypt'],
'dist_dir' : 'gam-64',
'compressed' : True}
}
)
)

View File

@@ -5,11 +5,12 @@ sys.argv.append('py2exe')
setup(
console = ['gam.py'],
zipfile = None,
options = {'py2exe':
{'optimize': 2,
'bundle_files': 1,
'includes': ['passlib.handlers.sha2_crypt']'
'dist_dir' : 'gam'}
}
)
)