How to run Azure VM CustomScriptExtension as domai

2019-09-13 10:50发布

Updated to explain my root problem: If Azure has extensions for VM's, as they are being provisioned, to join a domain, and to run scripts, how can I run a script as a domain user?

The script needs to be run as a domain user in order to access a file share to retrieve installation files and other scripts that are neither part of the VM template image nor can (reasonably) be uploaded to Azure blob storage and downloaded as part of provisioning.

I split this question in two because the 2nd half (represented here) didn't get solved.

What I have working is a Powershell script that takes a JSON file to create a new VM; the JSON file contains instructions for the VM to join a domain and run a custom script. Both things do happen, but the script runs as the user workgroup\system and therefore doesn't have access to a network drive.

  • How can I best provide a specific user's credentials for such a script?

I'm trying to have the script spawn a new Powershell session with the credentials of a different user, but I'm having a hard time figuring out the syntax -- I can't even get it to work on my development workstation. Naturally, security is a concern but if I could get this to work using encrypted stored credentials, this might be acceptable.

... but don't limit your answers -- maybe there's an entirely different way to go about this and achieve the same effect?

Param(
    [switch]$sudo, # Indicates we've already tried to elevate to admin
    [switch]$su # Indicates we've already tried to switch to domain user
)

try {

    # Pseudo-constants
    $DevOrProd=(Get-Item $MyInvocation.MyCommand.Definition).Directory.Parent.Name
    $PsScriptPath = Split-Path -parent $MyInvocation.MyCommand.Definition
    $pathOnPDrive = "\\dkfile01\P\SoftwareTestData\Azure\automation\$DevOrProd\run-once"
    $fileScriptLocal = $MyInvocation.MyCommand.Source
    $fileScriptRemote = "$pathOnPDrive\run-once-from-netdrive.ps1"
    # $filePw = "$pathOnPDrive\cred.txt"
    $fileLog="$PsScriptPath\switch-user.log"
    $Myuser="mohican"
    $Myuserpass="alhambra"
    $Mydomainuser="mydomain\$Myuser"
    $Mydomain="mydomain.com"

    # Check variables
    write-output("SUDO=[$SUDO]")
    write-output("SU=[$SU]")

    # Functions
    function Test-Admin {
      $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
      return ($currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator))
    }

    # Main
    write-output("Run-once script starting ...")

    # Check admin privilege
    write-output("Checking admin privilege ...")
    if (Test-Admin) {
        write-output("- Is admin.")
    } else {
        write-output("- Not an admin.")
        if ($sudo) {
            write-output("  - Already tried elevating, didn't work.")
            write-output("Run-once script on local VM finished.")
            write-output("")
            exit(0) # Don't return failure exit code because Azure will report it as if the deployment broke...
        } else {
            write-output("  - Attempting to elevate ...")
            $arguments = "-noprofile -file $fileScriptLocal"
            $arguments = $arguments +" -sudo"
            try {
                Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments
                write-output("    - New process started.")
            } catch {
                write-output("    - New process failed to start.")
            }
            write-output("Run-once script on local VM finished.")
            write-output("")
            exit(0) # The action will continue in the spawned process
        }
    }
    write-output("Checked admin privilege ... [OK]")

    # Check current user
    write-output("Checking user account ...")
    $hostname = $([Environment]::MachineName).tolower()
    $domainname = $([Environment]::UserDomainName).tolower()
    $thisuser = $([Environment]::UserName).tolower()
    write-output("- Current user is ""$domainname\$thisuser"" on ""$hostname"".")
    write-output("- Want to be user ""$Myuser"".")
    if ($Myuser -eq $thisuser) {
        write-output("  - Correct user.")
    } else {
        write-output("  - Incorrect user.")
        if ($su) {
            write-output("  - Already tried switching user, didn't work.")
            write-output("Run-once script on local VM finished.")
            write-output("")
            exit(0) # Don't return failure exit code because Azure will report it as if the deployment broke...
        } else {
            write-output("  - Attempting to switch to user ""$Mydomainuser"" with passwond ""$Myuserpass"" ...")
            # FIXME -- This does not work... :-(
            $MyuserpassSecure = ConvertTo-SecureString $Myuserpass -AsPlainText -Force
            $credential = New-Object System.Management.Automation.PSCredential $Mydomainuser, $MyuserpassSecure
            $arguments = "-noprofile -file $fileScriptLocal"
            $arguments = $arguments +" -sudo -su -Credential $credential -computername $hostname"
            try {
                Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments
                write-output("    - New process started.")
            } catch {
                write-output("    - New process failed to start.")
            }
            write-output("Run-once script on local VM finished.")
            write-output("")
            exit(0) # The action will continue in the spawned process
        }
    }
    write-output("Checked user account ... [OK]")

    # Run script from P: drive (finally!)
    write-output("Attempting to run script from P: drive ...")
    write-output("- Script file: ""$fileScriptRemote""")
    if (test-path $fileScriptRemote) {
        write-output("Running script from P: drive ...")
        $arguments = "-noprofile -file $fileScriptRemote"
        try {
            Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments
            write-output("    - New process started.")
        } catch {
            write-output("    - New process failed to start.")
        }
        write-output("Run-once script on local VM finished.")
        write-output("")
        exit(0) # The action will continue in the spawned process
    } else {
        write-output("- Could not locate/access script file!")
        write-output("Ran script from P: drive ... [ERROR]")
    }

    write-output("Run-once script on local VM finished.")
    write-output("")

} catch {
    write-warning("Unhandled error in line $($_.InvocationInfo.ScriptLineNumber): $($error[0])")
    write-output("ABEND")
    write-output("")
}

1条回答
Fickle 薄情
2楼-- · 2019-09-13 11:30

There's a couple of parts to this question as well!

Firstly getting credentials there, at some point you are going to need to pass a credential to the machine, even if it is a credential to obtain the credentials.

My personal solution is to create a certificate to encrypt a PSCredential object, store that object on a HTTP server, then pass the certificate and pfx password in the script. Of course if you're prebuilding the servers, you can preinstall this certificate. (there is a code review question with the code for this)

Alternatively you might be able to use something like Azure Key Vault to store the pfx password.

For the runas part. There are a few options

I've not launched Powershell as a different user since about v1! so I'll hope somebody else talks about that one.

You could run a scheduled task that logs in as a different user, this should work.

If you're running under a different context you can set the autologin properties, reboot the machine let its scripts run then delete the autologin entry and reboot again. This gives the added benefit that you can have a specific severely limited domain account that only has access to the shares you need it to, that has its admin / login rights stripped from each machine once it is built. This way you can also keep all of your build scripts in Active Directory and let that user automatically pull them down and run.

查看更多
登录 后发表回答