text: #!/usr/bin/env python
  
  # Copyright 2015 Google Inc. 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.
  
  import base64
  import copy
  import datetime
  import json
  import time
  import argparse
  
  # PyCrypto library: https://pypi.python.org/pypi/pycrypto
  from Crypto.Cipher import PKCS1_OAEP
  from Crypto.PublicKey import RSA
  from Crypto.Util.number import long_to_bytes
  
  # Google API Client Library for Python:
  # https://developers.google.com/api-client-library/python/start/get_started
  from oauth2client.client import GoogleCredentials
  from googleapiclient.discovery import build
  
  
  def GetCompute():
      """Get a compute object for communicating with the Compute Engine API."""
      credentials = GoogleCredentials.get_application_default()
      compute = build("compute", "v1", credentials=credentials)
      return compute
  
  
  def GetInstance(compute, instance, zone, project):
      """Get the data for a Google Compute Engine instance."""
      cmd = compute.instances().get(instance=instance, project=project, zone=zone)
      return cmd.execute()
  
  
  def GetKey():
      """Get an RSA key for encryption."""
      # This uses the PyCrypto library
      key = RSA.generate(2048)
      return key
  
  
  def GetModulusExponentInBase64(key):
      """Return the public modulus and exponent for the key in bas64 encoding."""
      mod = long_to_bytes(key.n)
      exp = long_to_bytes(key.e)
  
      modulus = base64.b64encode(mod)
      exponent = base64.b64encode(exp)
  
      return modulus, exponent
  
  
  def GetExpirationTimeString():
      """Return an RFC3339 UTC timestamp for 5 minutes from now."""
      utc_now = datetime.datetime.utcnow()
      # These metadata entries are one-time-use, so the expiration time does
      # not need to be very far in the future. In fact, one minute would
      # generally be sufficient. Five minutes allows for minor variations
      # between the time on the client and the time on the server.
      expire_time = utc_now + datetime.timedelta(minutes=5)
      return expire_time.strftime("%Y-%m-%dT%H:%M:%SZ")
  
  
  def GetJsonString(user, modulus, exponent, email):
      """Return the JSON string object that represents the windows-keys entry."""
  
      converted_modulus = modulus.decode("utf-8")
      converted_exponent = exponent.decode("utf-8")
  
      expire = GetExpirationTimeString()
      data = {
          "userName": user,
          "modulus": converted_modulus,
          "exponent": converted_exponent,
          "email": email,
          "expireOn": expire,
      }
  
      return json.dumps(data)
  
  
  def UpdateWindowsKeys(old_metadata, metadata_entry):
      """Return updated metadata contents with the new windows-keys entry."""
      # Simply overwrites the "windows-keys" metadata entry. Production code may
      # want to append new lines to the metadata value and remove any expired
      # entries.
      new_metadata = copy.deepcopy(old_metadata)
      new_metadata["items"] = [{"key": "windows-keys", "value": metadata_entry}]
      return new_metadata
  
  
  def UpdateInstanceMetadata(compute, instance, zone, project, new_metadata):
      """Update the instance metadata."""
      cmd = compute.instances().setMetadata(
          instance=instance, project=project, zone=zone, body=new_metadata
      )
      return cmd.execute()
  
  
  def GetSerialPortFourOutput(compute, instance, zone, project):
      """Get the output from serial port 4 from the instance."""
      # Encrypted passwords are printed to COM4 on the windows server:
      port = 4
      cmd = compute.instances().getSerialPortOutput(
          instance=instance, project=project, zone=zone, port=port
      )
      output = cmd.execute()
      return output["contents"]
  
  
  def GetEncryptedPasswordFromSerialPort(serial_port_output, modulus):
      """Find and return the correct encrypted password, based on the modulus."""
      # In production code, this may need to be run multiple times if the output
      # does not yet contain the correct entry.
  
      converted_modulus = modulus.decode("utf-8")
  
      output = serial_port_output.split("\n")
      for line in reversed(output):
          try:
              entry = json.loads(line)
              if converted_modulus == entry["modulus"]:
                  return entry["encryptedPassword"]
          except ValueError:
              pass
  
  
  def DecryptPassword(encrypted_password, key):
      """Decrypt a base64 encoded encrypted password using the provided key."""
  
      decoded_password = base64.b64decode(encrypted_password)
      cipher = PKCS1_OAEP.new(key)
      password = cipher.decrypt(decoded_password)
      return password
  
  
  def Arguments():
      # Create the parser
      args = argparse.ArgumentParser(description="List the content of a folder")
  
      # Add the arguments
      args.add_argument(
          "--instance", metavar="instance", type=str, help="compute instance name"
      )
  
      args.add_argument("--zone", metavar="zone", type=str, help="compute zone")
  
      args.add_argument("--project", metavar="project", type=str, help="gcp project")
  
      args.add_argument("--username", metavar="username", type=str, help="username")
  
      args.add_argument("--email", metavar="email", type=str, help="email")
  
      # return arguments
      return args.parse_args()
  
  
  def main():
      config_args = Arguments()
  
      # Setup
      compute = GetCompute()
      key = GetKey()
      modulus, exponent = GetModulusExponentInBase64(key)
  
      # Get existing metadata
      instance_ref = GetInstance(
          compute, config_args.instance, config_args.zone, config_args.project
      )
      old_metadata = instance_ref["metadata"]
      # Create and set new metadata
      metadata_entry = GetJsonString(
          config_args.username, modulus, exponent, config_args.email
      )
      new_metadata = UpdateWindowsKeys(old_metadata, metadata_entry)
  
      # Get Serial output BEFORE the modification
      serial_port_output = GetSerialPortFourOutput(
          compute, config_args.instance, config_args.zone, config_args.project
      )
  
      UpdateInstanceMetadata(
          compute,
          config_args.instance,
          config_args.zone,
          config_args.project,
          new_metadata,
      )
  
      # Get and decrypt password from serial port output
      # Monitor changes from output to get the encrypted password as soon as it's generated, will wait for 30 seconds
      i = 0
      new_serial_port_output = serial_port_output
      while i <= 30 and serial_port_output == new_serial_port_output:
          new_serial_port_output = GetSerialPortFourOutput(
              compute, config_args.instance, config_args.zone, config_args.project
          )
          i += 1
          time.sleep(1)
  
      enc_password = GetEncryptedPasswordFromSerialPort(new_serial_port_output, modulus)
  
      password = DecryptPassword(enc_password, key)
      converted_password = password.decode("utf-8")
  
      # Display only the password
      print(format(converted_password))
  
  
  if __name__ == "__main__":
      main()
  