blob: fac5872c835a13c64126aae8b980370dd4e3888c [file] [log] [blame]
#!/usr/bin/python
# Copyright 2017 The Cobalt Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
""" This script provides an API for the Xbox REST API. """
from __future__ import print_function
import base64
import os
import pprint
import sys
import time
import traceback
from distutils.util import strtobool # pylint: disable=deprecated-module
import requests
from requests.compat import urljoin
# This helps the script run in an consistent way across multiple different
# library versions(requests) and python runtime versions.
try:
from requests.packages import urllib3
except ImportError:
import urllib3
try:
from urllib3.exceptions import InsecureRequestWarning
urllib3.disable_warnings(InsecureRequestWarning)
except ImportError:
pass
try:
input_fn = raw_input
except NameError:
input_fn = input
_DEFAULT_URL_FORMAT = 'https://{}:{}}/'
_APPX_RELATIVE_PATH = 'appx'
_DEVELOPMENT_FILES = 'DevelopmentFiles'
_LOOSE_APPS = 'LooseApps'
_LOCAL_APP_DATA = 'LocalAppData'
_LOCAL_CACHE_FOLDERNAME = r'\\LocalCache'
_TEMP_FILE_FOLDERNAME = r'\\WdpTempWebFolder'
_DEFAULT_STAGING_APP_NAME = 'appx'
_APP_INSTALLATION_STATUS_ENDPOINT = '/api/app/packagemanager/state'
_DELETE_APP_ENDPOINT = '/api/app/packagemanager/package'
_DELETE_FILE_ENDPOINT = '/api/filesystem/apps/file'
_DEVICE_INFO_ENDPOINT = '/ext/xbox/info'
_EXT_USER_ENDPOINT = '/ext/user'
_GET_FILE_ENDPOINT = '/api/filesystem/apps/file'
_GET_FILES_ENDPOINT = '/api/filesystem/apps/files'
_KILL_PROCESS_ENDPOINT = 'api/taskmanager/process'
_LIST_ALL_APPS_ENDPOINT = '/api/app/packagemanager/packages'
_MACHINENAME_ENDPOINT = '/api/os/machinename'
_NETWORK_CREDS_ENDPOINT = '/ext/networkcredential'
_REGISTER_ENDPOINT = 'api/app/packagemanager/register'
_RESOURCE_MANAGER_PROCESSES_ENDPOINT = 'api/resourcemanager/processes'
_SANDBOX_ENDPOINT = '/ext/xboxlive/sandbox'
_SETTINGS_ENDPOINT = '/ext/settings'
_OS_UPDATE_ENDPOINT = '/ext/settings/osupdatepolicy'
_TASKMANAGER_ENDPOINT = '/api/taskmanager/app'
_UPLOAD_FILE_ENDPOINT = '/api/filesystem/apps/file'
_UPLOAD_FOLDER_ENDPOINT = '/api/app/packagemanager/upload'
_DEPLOY_INFO_ENDPOINT = '/ext/app/deployinfo'
_DEVELOPER_FOLDER_ENDPOINT = '/ext/smb/developerfolder'
# This header is required for all non-GET requests for windows
# dev portal. It must be populated with the value of a specific cookie.
_CSRF_TOKEN_HEADER = 'X-CSRF-Token'
_INSTALL_FINISH_TIMEOUT_SECONDS = 90.0
_PROCESS_RUN_TIMEOUT_SECONDS = 300.0
_PROCESS_KILL_TIMEOUT_SECONDS = 20.0
# Wait time to use when polling the same API repeatedly.
_API_RETRY_SECONDS = 2.0
# Returns a dictionary of "directory_name" -> [file_paths,...]
def GetFilesRecursively(path):
path = os.path.abspath(path)
all_file_paths = []
for root, _, filenames in os.walk(path):
for filename in filenames:
all_file_paths.append(os.path.join(root, filename))
out = {}
for file_path in all_file_paths:
if os.path.isdir(file_path):
continue
dir_name = os.path.dirname(file_path)
if dir_name not in out:
out[dir_name] = []
out[dir_name].append(file_path)
return out
def _PrintAndRaiseIfError(response):
"""If http response is not OK (200), then print information and raise error
If the response is not http "OK", then an exception is raised by
|raise_for_status|, and this function will print additional information
such as the request url, the response headers, and the response content.
After that the exception is re-raised so that it can be handled at a higher
level. This function is mainly useful for debugging failed requests.
Args:
response: Server's response to our http request.
Raises:
requests.exceptions.HTTPError
Returns:
None
"""
try:
#The following line only raises on a response that is not status OK(200).
response.raise_for_status()
except Exception as e:
print('URL:', response.url)
print('status_code: ' + str(response.status_code))
print('Headers:')
pprint.pprint(dict(response.headers))
print('Response:')
try:
pprint.pprint(response.json())
except Exception: #pylint: disable=broad-except
print(response.text)
raise e
class MissingDependencyError(Exception):
pass
# Xb1NetworkApi allows an interface for communicating with a remote xbox
# device.
class Xb1NetworkApi:
""" This header is required for all non-GET requests for windows
dev portal. It must be populated with the value of a specific cookie."""
_CSRF_TOKEN_HEADER = 'X-CSRF-Token'
def __init__(self, device_id, port):
#? self.out_directory = os.path.split(self.GetTargetPath())[0]
self.web_portal_url = f'https://{device_id}:{port}/'
self._logfile_name = None
self._csrf_token = None
self._cookie_jar = None
self.logging_function = None
self.cached_package_name_map = {}
self._AuthenticateOnce()
def SetLoggingFunction(self, logging_function):
self.logging_function = logging_function
def Log(self, s):
if self.logging_function:
self.logging_function(s)
else:
print(s, end='')
def LogLn(self, s):
self.Log(s + '\n')
def ComputerName(self):
return self._DoJsonRequest('GET', _MACHINENAME_ENDPOINT)['ComputerName']
def GetInstalledPackages(self):
installed_apps_response = self._DoJsonRequest('GET',
_LIST_ALL_APPS_ENDPOINT)
return installed_apps_response['InstalledPackages']
# Returns all running processes in the app.
def GetRunningProcesses(self):
response_decoded = self._DoJsonRequest(
'GET', _RESOURCE_MANAGER_PROCESSES_ENDPOINT)
if 'Processes' in response_decoded:
return response_decoded['Processes']
return {}
def GetSettings(self):
val = self._DoJsonRequest('GET', _SETTINGS_ENDPOINT)
return val.get('Settings', [])
def IsPendingOSUpdate(self):
"""Detects whether there is a pending OS update."""
try:
self._DoRequest('GET', _OS_UPDATE_ENDPOINT)
# Value returned is: <Response [200]>
# So no pending update.
return False
except ValueError:
# If there is a pending OS update then the following exception is
# generated:
# URL: https://172.31.165.44:11443/ext/settings/osupdatepolicy
# status_code: 500
# Headers:
# {'Content-Length': '0',
# 'Content-Type': 'text/plain',
# 'Date': 'Thu, 09 May 2019 18:10:59 GMT',
# 'Server': 'Microsoft-HTTPAPI/2.0',
# 'Set-Cookie': 'CSRF-Token=YNsSUe/7hKTHsW/o/goM1opGEnYb6gi/'}
# In this case there is a response code with error 500.
return True
except Exception as err: #pylint: disable=broad-except
traceback.print_exc()
print(f'Unexpected {err}')
return True
def GetSetting(self, name):
settings = self.GetSettings()
for setting in settings:
if setting.get('Name', '') == name:
return setting
return {}
def IsInDevMode(self):
setting = self.GetSetting('DefaultApp')
return setting.get('Value', '') == 'Dev Home'
def GetDeviceInfo(self):
return self._DoJsonRequest('GET', _DEVICE_INFO_ENDPOINT)
def GetLiveSandboxInfo(self):
return self._DoRequest('GET', _SANDBOX_ENDPOINT).text
def GetNetworkCredentials(self):
val = self._DoJsonRequest('GET', _NETWORK_CREDS_ENDPOINT)
if val.get('Credentials'):
return (val['Username'], val['Password'], val['Path'])
return ()
def AddNetworkCredentials(self, username, password, path):
p = {'Username': username, 'Password': password, 'NetworkPath': path}
return self._DoJsonRequest('POST', _NETWORK_CREDS_ENDPOINT, params=p)
def GetSandbox(self):
return self._DoRequest('GET', '/ext/xboxlive/sandbox')
def GetXboxLiveUserInfos(self):
return self._DoJsonRequest('GET', _EXT_USER_ENDPOINT)
def GetUserId(self, email_address):
email_address = email_address.lower()
users = self.GetXboxLiveUserInfos().get('Users', [])
if not users:
self.LogLn('Error - no users logged in')
return
for user in users:
if user.get('EmailAddress', '').lower() == email_address:
return user.get('UserId', None)
def Restart(self):
return self._DoRequest('POST', '/api/control/restart')
def SetXboxLiveSignedInUserState(self, user_email, is_signed_in):
user_id = self.GetUserId(user_email)
if not user_id:
self.LogLn('Could not find any users registered on this device')
return
creds = {
'Users': [{
'UserId': int(user_id),
'SignedIn': bool(strtobool(str(is_signed_in))),
},]
}
rtn = self._DoRequest('PUT', _EXT_USER_ENDPOINT, json=creds)
return rtn
def GetXboxLiveUserIsLoggedIn(self, user_email):
user_email = user_email.lower()
infos = self.GetXboxLiveUserInfos().get('Users', [])
for info in infos:
if info.get('EmailAddress', '').lower() == user_email:
return info.get('SignedIn', False)
return False
def GetDeveloperFolderCredentials(self):
d = self._DoJsonRequest('GET', _DEVELOPER_FOLDER_ENDPOINT)
return (d['Username'], d['Password'], d['Path'])
def GetDeploymentInfo(self, partial_package_name):
full_package_name = self.ResolvePackageFullName(partial_package_name)
if not full_package_name:
self.LogLn('Could not get deployment info for ' + partial_package_name)
full_package_name = partial_package_name
return None
body = {
'DeployInfo': [{
'PackageFullName': full_package_name
},],
}
val = self._DoJsonRequest('POST', _DEPLOY_INFO_ENDPOINT, json=body)
return val
def IsBinaryRunning(self, target_name):
response_decoded = self._DoJsonRequest(
'GET', _RESOURCE_MANAGER_PROCESSES_ENDPOINT)
running_processes = response_decoded['Processes']
exe_name = target_name + '.exe'
filtered_processes = \
list(filter(lambda p: p['ImageName'] == exe_name, running_processes))
if len(filtered_processes) == 0:
return False
process_under_test = filtered_processes[0]
return process_under_test.get('IsRunning', False)
def KillProcess(self, pid):
self._DoJsonRequest('DELETE', _KILL_PROCESS_ENDPOINT, params={'pid': pid})
def WaitForBinaryToFinishRunning(self, target_name, timeout_seconds):
"""Waits up to |timeout_seconds| for |self.target_name| to finish running.
Args:
timeout_seconds: Timeout in seconds.
"""
assert isinstance(timeout_seconds, float)
current_time = time.time()
max_time = current_time + timeout_seconds
while current_time <= max_time:
if not self.IsBinaryRunning(target_name):
return
time.sleep(_API_RETRY_SECONDS)
if self.IsBinaryRunning(target_name):
raise RuntimeError('Waiting for process to end timed out.')
def FetchFileContent(self, filename_to_grab):
"""Fetch a file from device and returns the content
This function fetches a file named |filename_to_grab|,
within the |_LOCAL_APP_DATA| folder.
Args:
filename_to_grab: name of the file to fetch.
"""
self.LogLn('Fetching log..')
package_full_name = self._GetPackageFullName()
if not package_full_name:
return
fetch_log_request = requests.get( # pylint: disable=missing-timeout
urljoin(self.web_portal_url, self._GET_FILE_ENDPOINT),
verify=False,
params={
'knownfolderid': self._LOCAL_APP_DATA,
'packagefullname': package_full_name,
'filename': filename_to_grab,
'path': self._LOCAL_CACHE_FOLDERNAME
})
_PrintAndRaiseIfError(fetch_log_request)
return fetch_log_request.text
# Fetch a file given the package and file path.
def FetchPackageFile(self,
package_name,
path_to_file,
raise_on_failure=True,
is_text=True):
"""Fetch a file from device and returns the content
Note that files associated with a running process will
fail to be fetched.
Args:
path_to_file: name of the file to fetch.
raise_on_failure: when True, failure to fetch the file will result in an
raised exception.
"""
filename = os.path.basename(path_to_file)
path = os.path.dirname(path_to_file)
package_full_name = self.ResolvePackageFullName(package_name)
if path:
path = '\\\\' + path
filename = '' + filename
url = urljoin(self.web_portal_url, _GET_FILE_ENDPOINT)
f = requests.get( # pylint: disable=missing-timeout
url,
verify=False,
params={
'knownfolderid': _LOCAL_APP_DATA,
'packagefullname': package_full_name,
'filename': filename,
'path': path
})
if raise_on_failure:
_PrintAndRaiseIfError(f)
if is_text:
return f.text
else:
return f.content
else:
try:
if is_text:
return f.text
else:
return f.content
except Exception: #pylint: disable=broad-except
return None
return None
def ExecuteBinary(self, partial_package_name: str,
app_alias_name: str) -> bool:
default_relative_name = self._GetDefaultRelativeId(partial_package_name)
if not default_relative_name or not '!' in default_relative_name:
raise IOError('Could not resolve package name "' + partial_package_name +
'"')
relative_package_name = default_relative_name.split('!')[0]
package_relative_id = relative_package_name + '!' + app_alias_name
appid_64 = base64.b64encode(package_relative_id.encode('UTF-8'))
package_64 = base64.b64encode(default_relative_name.encode('UTF-8'))
try:
self.LogLn('Executing: ' + package_relative_id)
response = self._DoJsonRequest(
'POST',
_TASKMANAGER_ENDPOINT,
params={
'appid': appid_64,
'package': package_64
},
raise_on_failure=False)
if not response or response == requests.codes.OK:
self.LogLn('Execution successful')
return True
self.LogLn('Execution not successful: ' + str(response))
except Exception as err:
err_msg = '\n Failed to run:\n ' + package_relative_id + \
'\n because of:\n' + str(err)
raise IOError(err_msg) from err
return False
# Given a package name, return all files + directories.
# Throws IOError if the app is locked.
def ListFilesAndDirs(self, package_name, knownfolderid, sub_path):
args = {}
if sub_path:
args['path'] = '\\\\' + sub_path
if package_name:
args['packagefullname'] = self.ResolvePackageFullName(package_name)
# Per api, knownfolder id must 'LocalAppData' whenever package
# name is used.
assert knownfolderid == 'LocalAppData'
# 'knownfolderid' is required
args['knownfolderid'] = '' + knownfolderid
response = self._DoRequest(
'GET',
'/api/filesystem/apps/files',
params=args,
raise_on_failure=False)
if response.status_code == 403:
raise IOError('Could not list files for ' + package_name +
': permission denied.\n' + str(response))
response_json = response.json()
sub_files = []
sub_dirs = []
if 'Items' in response_json:
for item in response_json['Items']:
name = item['Name']
sub_path = item['SubPath']
if sub_path.endswith(name):
sub_dirs.append(name)
else:
sub_files.append(name)
return sub_files, sub_dirs
def ListPackageFilesAndDirs(self, package_name, sub_path=None):
return self.ListFilesAndDirs(package_name, 'LocalAppData', sub_path)
# Returns files, dirs
def ListPackageFilesAndDirsRecursive(self,
package_name,
list_empty_dirs=True):
package_name = self.ResolvePackageFullName(package_name)
def RecursiveStep(curr_dir_stack, found_files, found_dirs):
curr_dir = '/'.join(curr_dir_stack)
files, dirs = self.ListPackageFilesAndDirs(
package_name, sub_path=curr_dir)
add_curr_dir = curr_dir and (files or list_empty_dirs)
if add_curr_dir:
found_dirs.append(curr_dir)
for f in files:
tmp_path = curr_dir_stack[:]
tmp_path.append(f)
tmp_path = '/'.join(tmp_path)
found_files.append(tmp_path)
for d in dirs:
next_dir_stack = curr_dir_stack[:] # Copy by value.
next_dir_stack.append(d)
RecursiveStep(next_dir_stack, found_files, found_dirs)
curr_dir_stack = []
found_files = []
found_dirs = []
RecursiveStep(curr_dir_stack, found_files, found_dirs)
return found_files, found_dirs
def DeleteFile(self, known_folder_id, path, filename_to_delete):
"""Deletes a file named |file|.
This function deletes a file.
Args:
known_folder_id: name of the top level known folder
path: path to the directory containing filename_to_delete
filename_to_delete: name of the file to delete
"""
self.LogLn(f'Deleting [{filename_to_delete}]')
file_listing = self._DoJsonRequest(
'GET',
_GET_FILES_ENDPOINT,
params={
'knownfolderid': known_folder_id,
'path': path
})
for item in file_listing['Items']:
if item['Name'] == filename_to_delete:
break
else:
self.LogLn(filename_to_delete + ' was not found. Skipping delete step.')
return
self.LogLn('File found. Deleting.')
self._DoJsonRequest(
'DELETE',
_DELETE_FILE_ENDPOINT,
params={
'knownfolderid': known_folder_id,
'filename': filename_to_delete,
'path': path
})
def ClearDevFiles(self, path):
file_listing = self._DoJsonRequest(
'GET',
_GET_FILES_ENDPOINT,
params={
'knownfolderid': _DEVELOPMENT_FILES,
'path': path
})
for file in file_listing['Items']:
self.DeleteFile(_DEVELOPMENT_FILES, _TEMP_FILE_FOLDERNAME, file['Name'])
def ClearTempFiles(self):
self.ClearDevFiles(_TEMP_FILE_FOLDERNAME)
def ClearLooseAppFiles(self):
self.ClearDevFiles(_LOOSE_APPS)
def FindPackage(self, package_name):
all_packages = self.GetInstalledPackages()
for package in all_packages:
if package_name in package['PackageRelativeId']:
return package
for package in all_packages:
if package_name in package['PackageFullName']:
return package
for package in all_packages:
if package_name in package['PackageFamilyName']:
return package
return None
def ResolvePackageFullName(self, package_name):
if package_name not in self.cached_package_name_map:
package = self.FindPackage(package_name)
full_package_name = None
if package:
full_package_name = package['PackageFullName']
self.cached_package_name_map[package_name] = full_package_name
return self.cached_package_name_map[package_name]
def _GetDefaultRelativeId(self, partial_package_name):
package = self.FindPackage(partial_package_name)
if package is None:
return None
return package.get('PackageRelativeId', None)
def _DoRequest(self, method, endpoint, **kwargs):
url_to_request = urljoin(self.web_portal_url, endpoint)
headers = {}
raise_on_failure = True
if 'headers' in kwargs:
headers = kwargs['headers']
del kwargs['headers']
if 'raise_on_failure' in kwargs:
raise_on_failure = kwargs['raise_on_failure']
del kwargs['raise_on_failure']
cookie_jar = None
if method != 'GET':
cookie_jar = self._cookie_jar
if not self._csrf_token:
raise RuntimeError('CSRF token is required for all non-GET requests.')
headers.update({_CSRF_TOKEN_HEADER: self._csrf_token})
request_info = requests.request(
method,
url_to_request,
verify=False,
timeout=(60, 60),
cookies=cookie_jar,
headers=headers,
**kwargs)
if raise_on_failure:
_PrintAndRaiseIfError(request_info)
if request_info.cookies:
self._cookie_jar = request_info.cookies
self._csrf_token = self._cookie_jar['CSRF-Token']
return request_info
def _DoJsonRequest(self, method, endpoint, **kwargs):
response = self._DoRequest(method, endpoint, **kwargs)
if response is None:
return {}
try:
return response.json()
except ValueError:
return {}
def _AuthenticateOnce(self):
if self._csrf_token:
return
# Cookies / Authentication is a side effect of calling _DoRequest()
self._DoRequest('GET', _MACHINENAME_ENDPOINT)
if not self._csrf_token:
raise RuntimeError('Authentication failed.')
############################################
# A little tester to test + inspect packages
def InteractiveCommandLineMode():
# Local functions used in this test.
def PrintCurrentlyRunningProcesses(network_api):
running_processes = network_api.GetRunningProcesses()
print('Found ' + str(len(running_processes)) + ' processes:')
columns = ['ImageName', 'UserName', 'PageFileUsage', 'ProcessId']
size_dict = {}
for p in running_processes:
for key in columns:
val = p[key]
prev_val_size = size_dict.get(key, 0)
val_size = len(str(val))
if prev_val_size <= val_size:
size_dict[key] = val_size
header = ''
for key in columns:
n_size = max(len(key), size_dict.get(key, 0)) + 2
size_dict[key] = n_size
header += key.upper().rjust(n_size)
print(header)
for _ in range(0, len(header)):
print('-', end='')
print()
for p in running_processes:
msg = ''
for key in columns:
val = p[key]
msg += str(val).rjust(size_dict[key])
print(msg)
def PrintInstalledPackage(network_api):
packages = network_api.GetInstalledPackages()
print('Apps currently installed:')
for p in packages:
print(' ' + p['PackageFamilyName'])
def FindThenPrintPackage(network_api):
package_name = input_fn('Package name to inspect: ')
package = network_api.FindPackage(package_name)
if package is None:
print('Could not find package ' + package_name)
else:
pprint.pprint(package)
def ListFilesAndDirsInPackage(network_api):
package_name = input_fn('Package name: ')
package = network_api.FindPackage(package_name)
if package is None:
print('Could not find package ' + package_name)
else:
try:
files, dirs = network_api.ListPackageFilesAndDirs(package_name)
print(f'Files in {package_name}:\n')
pprint.pprint({'files': files, 'dirs': dirs})
except IOError as ioe:
print(ioe)
def PromptAndRunExecutable(network_api):
package_name = input_fn('Package name: ')
app_name = input_fn('App name: ')
try:
print('Attempting to run: ' + app_name + ' in ' + package_name)
network_api.ExecuteBinary(package_name, app_name)
except Exception as e: #pylint: disable=broad-except
print('Could not run binary because: ' + str(e))
def PromptAndSignInUserToXboxLive(network_api):
network_api.GetXboxLiveUserInfos()
email_addr = input_fn('User EmailAddress: ')
signed_in_state = input_fn('SignInState: ')
network_api.SetXboxLiveSignedInUserState(email_addr, signed_in_state)
def PromptPackageAndListFilesAndDirsRecursively(network_api):
package_name = input_fn('Package name: ')
files, dirs = network_api.ListPackageFilesAndDirsRecursive(package_name)
if files:
print('Files:')
for f in files:
print(' ' + f)
if dirs:
print('Directories:')
for d in dirs:
print(' ' + d)
# Begin Test.
ip = input_fn('IP Address of XBOX: ')
if len(ip.split('.')) != 4:
print('Expected ip to have a 4 numbers')
return
port = input_fn('port(default is 11443): ')
if len(port) == 0:
port = '11443'
network_api = Xb1NetworkApi(ip, port)
while True:
print('What would you like to do?')
print(' [0] See all currently running processes.')
print(' [1] Run executable.')
print(' [2] Find all installed packages.')
print(' [3] Inspect a package by name.')
print(' [4] List root directories and files in a package.')
print(' [5] Recursive list all files in package.')
print(' [6] Print System Settings.')
print(' [7] Print Device Info.')
print(' [8] Print Xbox Live Sandbox Mode.')
print(' [9] Print Network Credentials.')
print(' [10] XboxLive: Print signed in users.')
print(' [11] XboxLive: Sign in user.')
print(' [12] Print Network Credentials.')
print(' [13] Update Network Credentials.')
print(' [14] Print Deployment Info.')
print(' [15] Print whether OS Update is pending.')
print(' [q] Quit.')
val = input_fn('> ')
if val in ('', 'q'):
print('quitting.')
sys.exit(0)
elif '0' == val:
PrintCurrentlyRunningProcesses(network_api)
elif '1' == val:
PromptAndRunExecutable(network_api)
elif '2' == val:
PrintInstalledPackage(network_api)
elif '3' == val:
FindThenPrintPackage(network_api)
elif '4' == val:
ListFilesAndDirsInPackage(network_api)
elif '5' == val:
PromptPackageAndListFilesAndDirsRecursively(network_api)
elif '6' == val:
pprint.pprint(network_api.GetSettings())
elif '7' == val:
pprint.pprint(network_api.GetDeviceInfo())
elif '8' == val:
pprint.pprint(network_api.GetLiveSandboxInfo())
elif '9' == val:
pprint.pprint(network_api.GetNetworkCredentials())
elif '10' == val:
pprint.pprint(network_api.GetXboxLiveUserInfos())
elif '11' == val:
PromptAndSignInUserToXboxLive(network_api)
elif '12' == val:
pprint.pprint(network_api.GetNetworkCredentials())
elif '13' == val:
user_name = input_fn('username: ')
password = input_fn('password: ')
path = input_fn(
'path (blank = "D:\\DevelopmentFiles\\LooseApps\\appx"?): ')
if not path:
path = 'D:\\DevelopmentFiles\\LooseApps\\appx'
pprint.pprint(
network_api.AddNetworkCredentials(user_name, password, path))
elif '14' == val:
package_name = input_fn('Package Name: ')
pprint.pprint(network_api.GetDeploymentInfo(package_name))
elif '15' == val:
val = network_api.IsPendingOSUpdate()
if val:
print('----> OS has a pending update.\n')
else:
print('----> OS does NOT have a pending update.\n')
else:
print('Unexpected answer: ' + val)
input_fn('press the return key to continue....')
if __name__ == '__main__':
InteractiveCommandLineMode()