Compare commits

...

20 Commits
v4.0 ... v4.02

Author SHA1 Message Date
Jay Lee
b6b6824ee1 whatsnew for 4.02 2016-11-04 14:25:59 -04:00
Jay Lee
2902fc8931 Merge branch 'master' of https://github.com/jay0lee/GAM 2016-11-04 14:14:27 -04:00
Jay Lee
1a6ec398b2 gam calendar <> deleteevent 2016-11-04 14:14:21 -04:00
Ross Scroggs
7d849e0cc0 Code fixes, cleanup (#315)
* Code fixes, cleanup

Fix doCheckServiceAccount to pass correct api to buildGAPIServiceObject
pylint cleanup
Simplify passing login_hint to doRequestOAuth in main
Give error if invalid argument after gam <users> check

* Fix error

* Use try accept
2016-11-04 14:13:39 -04:00
Jay Lee
2958bd9f86 GAM 4.02 2016-11-04 12:02:35 -04:00
Jay Lee
15d93c9e5d pause at end of gam-setup.bat 2016-11-04 11:48:10 -04:00
Jay Lee
082c34b453 prompt for regular user to test service account. 2016-11-04 11:39:20 -04:00
Jay Lee
32e7932050 fix potential IndexError 2016-11-03 14:42:07 -04:00
Jay Lee
6becd08f3c fix login_hint issues 2016-11-03 14:30:14 -04:00
Jay Lee
26fbf9c524 don't run setup on upgrades 2016-11-03 14:20:15 -04:00
Ross Scroggs
d9124f3ffa Fix doRequestOAuth to find proper client secrets JSON file (#313)
The environment variables GAMUSERCONFIGDIR and CLIENTSECRETS are
factored in to GC_Values[GC_CLIENT_SECRETS_JSON]. On a new
installation, it will have the value
os.path.join(GM_Globals[GM_GAM_PATH], FN_CLIENT_SECRETS_JSON) but on an
existing installation the environment variables may have it located
somewhere else.
2016-11-03 13:50:43 -04:00
Jay Lee
aa1db89bd3 include gam-setup.bat in MSI and zip. Call it from MSI at end of install. 2016-11-03 13:29:26 -04:00
Jay Lee
ff3a8644ec Merge branch 'master' of https://github.com/jay0lee/GAM 2016-11-03 12:49:32 -04:00
Jay Lee
4721469b1d prettify oauth2.txt 2016-11-03 12:49:19 -04:00
Jay Lee
c4a3d29964 Easier to copy scope list 2016-11-03 12:48:46 -04:00
Jay Lee
1454526e65 gam-setup.bat file to automate Windows setup
part of #309
2016-11-03 11:38:23 -04:00
Ross Scroggs
2c0026512d Fix doGamVersion (#311)
* Fix doGamVersion

doGamVersion is called by showUsage with checkForArgs=False to keep
doGamVersion from wandering through some other command’s arguments

* Update documentation

* Further doGamVersion cleanup

Allow simple and check, keep pylint happy

* gam version simple is script only option, not exposed to user
2016-11-02 17:30:54 -04:00
Jay Lee
3c85da292e GAM 4.01 2016-11-02 15:28:15 -04:00
Jay Lee
c7b5251b03 prompt for admin email and use as hint 2016-11-02 15:23:43 -04:00
Jay Lee
5307a560bd fix service account create call 2016-11-02 14:45:17 -04:00
7 changed files with 219 additions and 48 deletions

View File

@@ -11,6 +11,8 @@ OPTIONS:
-a Architecture to install (i386, x86_64, arm). Default is to detect your arch with "uname -m".
-o OS we are running (linux, macos). Default is to detect your OS with "uname -s".
-p Profile update (true, false). Should script add gam command to environment. Default is true.
-u Admin user email address to use with GAM. Default is to prompt.
-r Regular user email address. Used to test service account access to user data. Default is to prompt.
-v Version to install (latest, prerelease, draft, 3.8, etc). Default is latest.
EOF
}
@@ -20,7 +22,9 @@ gamarch=$(uname -m)
gamos=$(uname -s)
update_profile=true
gamversion="latest"
while getopts "hd:a:o:p:v:" OPTION
adminuser=""
regularuser=""
while getopts "hd:a:o:p:u:r:v:" OPTION
do
case $OPTION in
h) usage; exit;;
@@ -28,6 +32,8 @@ do
a) gamarch=$OPTARG;;
o) gamos=$OPTARG;;
p) update_profile=$OPTARG;;
u) adminuser=$OPTARG;;
r) regularuser=$OPTARG;;
v) gamversion=$OPTARG;;
?) usage; exit;;
esac
@@ -180,7 +186,10 @@ while true; do
read -p "GAM is now installed. Are you ready to set up a Google API project for GAM? (yes or no) " yn
case $yn in
[Yy]*)
$target_dir/gam/gam create project
if [ "$adminuser" == "" ]; then
read -p "Please enter your G Suite admin email address: " adminuser
fi
$target_dir/gam/gam create project $adminuser
rc=$?
if (( $rc == 0 )); then
echo_green "Project creation complete."
@@ -205,7 +214,7 @@ while $project_created; do
read -p "Are you ready to authorize GAM to perform G Suite management operations as your admin account? (yes or no) " yn
case $yn in
[Yy]*)
$target_dir/gam/gam oauth create
$target_dir/gam/gam oauth create $adminuser
rc=$?
if (( $rc == 0 )); then
echo_green "Admin authorization complete."
@@ -230,8 +239,11 @@ while $project_created; do
read -p "Are you ready to authorize GAM to manage G Suite user data and settings? (yes or no) " yn
case $yn in
[Yy]*)
if [ "$regularuser" == "" ]; then
read -p "Please enter the email address of a regular G Suite user: " regularuser
fi
echo_yellow "Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console."
$target_dir/gam check serviceaccount
$target_dir/gam/gam user $adminuser check serviceaccount
rc=$?
if (( $rc == 0 )); then
echo_green "Service account authorization complete."

75
src/gam-setup.bat Normal file
View File

@@ -0,0 +1,75 @@
@echo(
@set /p adminemail= "Please enter your G Suite admin email address: "
:createproject
@echo(
@set /p yn= "Are you ready to set up a Google API project for GAM? [y or n] "
@if /I "%yn%"=="n" (
@ echo(
@ echo You can create an API project later by running:
@ echo(
@ echo gam create project
@ goto alldone
)
@if /I not "%yn%"=="y" (
@ echo(
@ echo Please answer y or n.
@ goto createproject
)
@gam create project %adminemail%
@if not ERRORLEVEL 1 goto projectdone
@echo(
@echo Projection creation failed. Trying again. Say n to skip projection creation.
@goto createproject
:projectdone
:adminauth
@echo(
@set /p yn= "Are you ready to authorize GAM to perform G Suite management operations as your admin account? [y or n] "
@if /I "%yn%"=="n" (
@ echo(
@ echo You can authorize an admin later by running:
@ echo(
@ echo gam oauth create %adminemail%
@ goto admindone
)
@if /I not "%yn%"=="y" (
@ echo(
@ echo Please answer y or n.
@ goto adminauth
)
@gam oauth create %adminemail%
@if not ERRORLEVEL 1 goto admindone
@echo(
@echo Admin authorization failed. Trying again. Say n to skip admin authorization.
@goto adminauth
:admindone
:saauth
@echo(
@set /p yn= "Are you ready to authorize GAM to manage G Suite user data and settings? [y or n] "
@if /I "%yn%"=="n" (
@ echo(
@ echo You can authorize a service account later by running:
@ echo(
@ echo gam user %adminemail% check serviceaccount
@ goto sadone
)
@if /I not "%yn%"=="y" (
@ echo(
@ echo Please answer y or n.
@ goto saauth
)
@echo(
@set /p regularuser= "Please enter the email address of a regular G Suite user: "
@echo Great! Checking service account scopes. This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console.
@gam user %regularuser% check serviceaccount
@if not ERRORLEVEL 1 goto sadone
@echo(
@echo Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization.
@goto saauth
:sadone
@echo GAM installation and setup complete!
:alldone
@pause

View File

@@ -23,7 +23,7 @@ For more information, see http://git.io/gam
"""
__author__ = u'Jay Lee <jay0lee@gmail.com>'
__version__ = u'4.0'
__version__ = u'4.02'
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
import sys
@@ -428,7 +428,7 @@ def indentMultiLineText(message, n=0):
return message.replace(u'\n', u'\n{0}'.format(u' '*n)).rstrip()
def showUsage():
doGAMVersion(checkForCheck=False)
doGAMVersion(checkForArgs=False)
print u'''
Usage: gam [OPTIONS]...
@@ -818,32 +818,32 @@ def doGAMCheckForUpdates(forceCheck=False):
except (urllib2.HTTPError, urllib2.URLError):
return
def doGAMVersion(checkForCheck=True):
def doGAMVersion(checkForArgs=True):
force_check = False
simple = False
i = 2
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace(u'_', u'')
force_check = True
if myarg == u'check':
force_check = True
i += 1
elif myarg == u'simple':
simple = True
i += 1
else:
print u'ERROR: %s is not a valid argument for "gam version"' % sys.argv[i]
sys.exit(2)
if checkForArgs:
i = 2
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace(u'_', u'')
if myarg == u'check':
force_check = True
i += 1
elif myarg == u'simple':
simple = True
i += 1
else:
print u'ERROR: %s is not a valid argument for "gam version"' % sys.argv[i]
sys.exit(2)
if simple:
sys.stdout.write(__version__)
sys.exit(0)
return
import struct
version_data = u'GAM {0} - {1}\n{2}\nPython {3}.{4}.{5} {6}-bit {7}\ngoogle-api-python-client {8}\n{9} {10}\nPath: {11}'
print version_data.format(__version__, GAM_URL, __author__, sys.version_info[0],
sys.version_info[1], sys.version_info[2], struct.calcsize(u'P')*8,
sys.version_info[3], googleapiclient.__version__, platform.platform(),
platform.machine(), GM_Globals[GM_GAM_PATH])
if checkForCheck or force_check:
sys.version_info[1], sys.version_info[2], struct.calcsize(u'P')*8,
sys.version_info[3], googleapiclient.__version__, platform.platform(),
platform.machine(), GM_Globals[GM_GAM_PATH])
if force_check:
doGAMCheckForUpdates(forceCheck=True)
def handleOAuthTokenError(e, soft_errors):
@@ -1214,17 +1214,17 @@ def doCheckServiceAccount(users):
for scope in scopes:
if scope in all_scopes:
continue # don't check same scope twice
all_scopes.append(scope)
all_scopes.append((api, scope))
all_scopes = sorted(all_scopes)
for scope in all_scopes:
try:
service = buildGAPIServiceObject(api, act_as=user, use_scopes=scope)
service = buildGAPIServiceObject(scope[0], act_as=user, use_scopes=scope[1])
service._http.request.credentials.refresh(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]))
result = u'PASS'
except oauth2client.client.HttpAccessTokenRefreshError:
result = u'FAIL'
all_scopes_pass = False
print u' Scope: {0:60} {1}'.format(scope, result)
print u' Scope: {0:60} {1}'.format(scope[1], result)
service_account = service._http.request.credentials.serialization_data[u'client_id']
if all_scopes_pass:
print u'\nAll scopes passed!\nService account %s is fully authorized.' % service_account
@@ -1241,7 +1241,7 @@ and grant Client name:
Access to scopes:
%s\n''' % (user_domain, service_account, ','.join(all_scopes))
%s\n''' % (user_domain, service_account, ',\n'.join([scope[1] for scope in all_scopes]))
sys.exit(int(not all_scopes_pass))
def showReport():
@@ -3318,6 +3318,42 @@ def doCalendarWipeData():
return
callGAPI(cal.calendars(), u'clear', calendarId=calendarId)
def doCalendarDeleteEvent():
calendarId, cal = buildCalendarGAPIObject(sys.argv[2])
if not cal:
return
events = []
sendNotifications = None
doit = False
i = 4
while (i < len(sys.argv)):
if sys.argv[i].lower() == u'notifyattendees':
sendNotifications = True
i += 1
elif sys.argv[i].lower() in [u'id', u'eventid']:
events.append(sys.argv[i+1])
i += 2
elif sys.argv[i].lower() in [u'query', u'eventquery']:
query = sys.argv[i+1]
result = callGAPIpages(cal.events(), u'list', items=u'items', calendarId=calendarId, q=query)
for event in result:
if u'id' in event:
events.append(event[u'id'])
i += 2
elif sys.argv[i].lower() == u'doit':
doit = True
i += 1
else:
print u'ERROR: %s is not a valid argument for gam calendar <email> delete event'
sys.exit(3)
if doit:
for eventId in events:
print u' deleting eventId %s' % eventId
callGAPI(cal.events(), u'delete', calendarId=calendarId, eventId=eventId)
else:
for eventId in events:
print u' would delete eventId %s. Add doit to command to actually delete event' % eventId
def doCalendarAddEvent():
calendarId, cal = buildCalendarGAPIObject(sys.argv[2])
if not cal:
@@ -6807,7 +6843,7 @@ def doCreateProject():
login_hint = sys.argv[3]
except IndexError:
while True:
login_hint = raw_input(u'What is your G Suite admin email address? ')
login_hint = raw_input(u'\nWhat is your G Suite admin email address? ')
if login_hint.find(u'@') == -1:
print u'Error: that is not a valid email address'
else:
@@ -6817,12 +6853,12 @@ def doCreateProject():
for i in range(3):
project_id += u'-%s' % ''.join(random.choice(string.digits + string.ascii_lowercase) for i in range(3))
project_name = u'project:%s' % project_id
scope=u'https://www.googleapis.com/auth/cloud-platform'
client_id=u'297408095146-fug707qsjv4ikron0hugpevbrjhkmsk7.apps.googleusercontent.com'
client_secret=u'qM3dP8f_4qedwzWQE1VR4zzU'
scope = u'https://www.googleapis.com/auth/cloud-platform'
client_id = u'297408095146-fug707qsjv4ikron0hugpevbrjhkmsk7.apps.googleusercontent.com'
client_secret = u'qM3dP8f_4qedwzWQE1VR4zzU'
flow = oauth2client.client.OAuth2WebServerFlow(client_id=client_id,
client_secret=client_secret, scope=scope, redirect_uri=oauth2client.client.OOB_CALLBACK_URN,
user_agent=GAM_INFO, access_type=u'online', response_type=u'code', login_hint=login_hint)
client_secret=client_secret, scope=scope, redirect_uri=oauth2client.client.OOB_CALLBACK_URN,
user_agent=GAM_INFO, access_type=u'online', response_type=u'code', login_hint=login_hint)
flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER])
storage_dict = {}
storage = DictionaryStorage(storage_dict, u'credentials')
@@ -6875,15 +6911,15 @@ and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup,
serveman = googleapiclient.discovery.build(u'servicemanagement', u'v1', http=http, cache_discovery=False)
apis = [u'admin-json.googleapis.com', u'appsactivity-json.googleapis.com', u'calendar-json.googleapis.com',
u'classroom.googleapis.com', u'drive', u'gmail-json.googleapis.com', u'groupssettings-json.googleapis.com',
u'licensing-json.googleapis.com', u'plus-json.googleapis.com', u'contacts-json.googleapis.com']
u'classroom.googleapis.com', u'drive', u'gmail-json.googleapis.com', u'groupssettings-json.googleapis.com',
u'licensing-json.googleapis.com', u'plus-json.googleapis.com', u'contacts-json.googleapis.com']
for api in apis:
print u' enabling API %s...' % api
enable_operation = callGAPI(serveman.services(), u'enable', serviceName=api, body={u'consumerId': project_name})
iam = googleapiclient.discovery.build(u'iam', u'v1', http=http, cache_discovery=False)
print u'Creating Service Account'
service_account = callGAPI(iam.projects().serviceAccounts(), u'create', name=u'projects/%s' % project_id,
body={u'accountId': project_id, u'serviceAccount': {u'displayName': u'GAM Project'}})
body={u'accountId': project_id, u'serviceAccount': {u'displayName': u'GAM Project'}})
body = {u'privateKeyType': u'TYPE_GOOGLE_CREDENTIALS_FILE', u'keyAlgorithm': u'KEY_ALG_RSA_4096'}
key = callGAPI(iam.projects().serviceAccounts().keys(), u'create', name=service_account[u'name'], body=body)
oauth2service_data = base64.b64decode(key[u'privateKeyData'])
@@ -6923,7 +6959,7 @@ and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup,
client_secrets_file = os.path.join(GM_Globals[GM_GAM_PATH], FN_CLIENT_SECRETS_JSON)
if os.path.isfile(client_secrets_file):
client_secrets_file = u'%s-%s' % (client_secrets_file, project_id)
writeFile(client_secrets_file, cs_data, continueOnError=False)
writeFile(client_secrets_file, cs_data, continueOnError=False)
print u'''Almost there! Now please switch back to your browser and:
1. Click OK to close "OAuth client" popup if it's still open.
@@ -9942,7 +9978,7 @@ OAUTH2_MENU += '''
OAUTH2_CMDS = [u's', u'u', u'e', u'c']
MAXIMUM_SCOPES = 28
def doRequestOAuth():
def doRequestOAuth(login_hint=None):
def _checkMakeScopesList(scopes):
del scopes[:]
for i in range(num_scopes):
@@ -9965,7 +10001,9 @@ def doRequestOAuth():
MISSING_CLIENT_SECRETS_MESSAGE = u"""Please configure OAuth 2.0
To make GAM run you will need to populate the {0} file found at:
{1}
with information from the APIs Console <https://console.developers.google.com>.
See this site for instructions:
@@ -9973,6 +10011,24 @@ See this site for instructions:
""".format(FN_CLIENT_SECRETS_JSON, GC_Values[GC_CLIENT_SECRETS_JSON], GAM_WIKI_CREATE_CLIENT_SECRETS)
cs_data = readFile(GC_Values[GC_CLIENT_SECRETS_JSON], mode=u'rb', continueOnError=True, displayError=True, encoding=None)
if not cs_data:
systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE)
try:
cs_json = json.loads(cs_data)
client_id = cs_json[u'installed'][u'client_id']
client_secret = cs_json[u'installed'][u'client_secret']
except (ValueError, IndexError, KeyError):
print u'ERROR: the format of your client secrets file:\n\n%s\n\n is incorrect. Please recreate the file.'
sys.exit(3)
if not login_hint:
while True:
login_hint = raw_input(u'\nWhat is your G Suite admin email address? ')
if login_hint.find(u'@') == -1:
print u'Error: that is not a valid email address'
else:
break
num_scopes = len(OAUTH2_SCOPES)
menu = OAUTH2_MENU % tuple(range(num_scopes))
selected_scopes = []
@@ -10034,19 +10090,20 @@ See this site for instructions:
status, message = _checkMakeScopesList(scopes)
if status:
break
try:
FLOW = oauth2client.client.flow_from_clientsecrets(GC_Values[GC_CLIENT_SECRETS_JSON], scope=scopes)
except oauth2client.client.clientsecrets.InvalidClientSecretsError:
systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE)
flow = oauth2client.client.OAuth2WebServerFlow(client_id=client_id,
client_secret=client_secret, scope=scopes, redirect_uri=oauth2client.client.OOB_CALLBACK_URN,
user_agent=GAM_INFO, access_type=u'offline', response_type=u'code', login_hint=login_hint)
storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT])
credentials = storage.get()
flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER])
if credentials is None or credentials.invalid:
http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL])
try:
credentials = oauth2client.tools.run_flow(flow=FLOW, storage=storage, flags=flags, http=http)
credentials = oauth2client.tools.run_flow(flow=flow, storage=storage, flags=flags, http=http)
except httplib2.CertificateValidationUnsupported:
noPythonSSLExit()
else:
print u'It looks like you\'ve already authorized GAM. Refusing to overwrite existing file:\n\n%s' % GC_Values[GC_OAUTH2_TXT]
def batch_worker():
while True:
@@ -10418,7 +10475,11 @@ def ProcessGAMCommand(args):
elif command in [u'oauth', u'oauth2']:
argument = sys.argv[2].lower()
if argument in [u'request', u'create']:
doRequestOAuth()
try:
login_hint = sys.argv[3]
except IndexError:
login_hint = None
doRequestOAuth(login_hint)
elif argument in [u'info', u'verify']:
OAuthInfo()
elif argument in [u'delete', u'revoke']:
@@ -10441,6 +10502,8 @@ def ProcessGAMCommand(args):
doCalendarWipeData()
elif argument == u'addevent':
doCalendarAddEvent()
elif argument == u'deleteevent':
doCalendarDeleteEvent()
else:
print u'ERROR: %s is not a valid argument for "gam calendar"' % argument
sys.exit(2)
@@ -10742,6 +10805,9 @@ def ProcessGAMCommand(args):
checkWhat = sys.argv[4].replace(u'_', '').lower()
if checkWhat == u'serviceaccount':
doCheckServiceAccount(users)
else:
print u'ERROR: %s is not a valid argument for "gam <users> check"' % checkWhat
sys.exit(2)
elif command == u'profile':
doProfile(users)
elif command == u'imap':

View File

@@ -52,6 +52,12 @@
<Component Id="whatsnew_txt" Guid="6aa9863c-90d9-412f-9b73-fda82549a950">
<File Name="whatsnew.txt" KeyPath="yes" />
</Component>
<Component Id="gam_setup_bat" Guid="ef01f93a-4b50-488a-9c04-ec5e13e66218">
<File Name="gam-setup.bat" KeyPath="yes" />
</Component>
<Component Id="gamcommands_txt" Guid="58ff9c45-a7c9-4e22-8845-a9a92610c1f3">
<File Name="gamcommands.txt" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
@@ -59,7 +65,10 @@
<InstallUISequence>
<ExecuteAction />
<Show Dialog="WelcomeDlg" Before="ProgressDlg" />
<!-- <Show Dialog="ProgressDlg" After="" /> -->
</InstallUISequence>
<CustomAction Id="setup_gam" ExeCommand="[INSTALLFOLDER]gam-setup.bat" Directory="INSTALLFOLDER" Execute="commit" Impersonate="yes" Return="asyncWait"/>
<InstallExecuteSequence>
<Custom Action="setup_gam" After="InstallFiles" >NOT Installed AND NOT UPGRADINGPRODUCTCODE AND NOT WIX_UPGRADE_DETECTED</Custom>
</InstallExecuteSequence>
</Fragment>
</Wix>

View File

@@ -273,7 +273,7 @@ class Credentials(object):
to_serialize[key] = val.decode('utf-8')
if isinstance(val, set):
to_serialize[key] = list(val)
return json.dumps(to_serialize)
return json.dumps(to_serialize, indent=4, sort_keys=True)
def to_json(self):
"""Creating a JSON representation of an instance of Credentials.

View File

@@ -1,3 +1,8 @@
GAM 4.02
- "gam create project" command simplifies creation of client_secrets.json and oauth2service.json project files.
- Automated wizard simplifies GAM setup on Linux and MacOS (coming soon to Windows MSI).
- "gam calendar <email> deleteevent" deletes events by ID or query
GAM 3.8
- Old GData APIs removed from GAM. Admin Settings and Email Audit commands are no longer included, keep a copy of GAM 3.72 around if you use them. All Email Settings commands now use new Gmail API.
- Updated httplib2, google-api-client, uritemplate libraries

View File

@@ -11,12 +11,16 @@ del /q /f gam.wixpdb
c:\python27-32\scripts\pyinstaller --clean -F --distpath=gam windows-gam.spec
xcopy LICENSE gam\
xcopy whatsnew.txt gam\
xcopy gam-setup.bat gam\
xcopy gamcommands.txt gam\
del gam\w9xpopen.exe
"%ProgramFiles%\7-Zip\7z.exe" a -tzip gam-%1-windows.zip gam\ -xr!.svn
c:\python27-64\scripts\pyinstaller --clean -F --distpath=gam-64 windows-gam.spec
xcopy LICENSE gam-64\
xcopy whatsnew.txt gam-64\
xcopy gam-setup.bat gam-64\
xcopy gamcommands.txt gam-64\
"%ProgramFiles%\7-Zip\7z.exe" a -tzip gam-%1-windows-x64.zip gam-64\ -xr!.svn
set GAMVERSION=%1