Search code examples
vb.netpowershellvisual-studio-2017credentialssecurestring

Using stored credentials in powershell through vb.net


I've written a pair of apps; one that issues powershell scripts to clients (the server) and one that executes powershell scripts passed to it (the client).

I've added functions to detect if a particular script requires elevation. In the event that a script requires elevation, the server app prompts the user for their credentials. The password is converted to a secure string and saved in a SQL database, as is the username along with the script.

The client app grabs the script and, if elevation is required, grabs the username and secure string then tries to build a credential object from it.

The functionality is working fine for non-elevated scripts, but elevated scripts are not working. Its not erroring or throwing an exception (finally) but the scripts are not executed.

The first part of this process reads the data from SQL into a datatable and then i loop through the rows of that datatable. Once I've got a row that contains a script that needs running, I build the script.

Here's how I'm building and executing the powershell in VB...

    If this_routine_elevation_required = 1 Then
        Dim scriptbody As String = row("scriptbody").ToString
        Dim elevated_user_un As String = row("elevated_user").ToString
        Dim elevated_user_ss As String = row("securestring").ToString
        credential_script = "$securepassword = '" & elevated_user_ss & "' | ConvertTo-SecureString; $username='" & elevated_user_un & "';$credential = New-Object System.Management.Automation.PsCredential($username, $securepassword)"
        action_response = RunPowershellScript(credential_script & "; " & scriptbody)

    End If

and here is the function that executes the powershell (using 'Imports System.Management.Automation)...

 Private Function RunPowershellScript(ByVal scriptText As String) As String

    ' create Powershell runspace 
    Dim MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()
    MyRunSpace.Open()
    Dim MyPipeline As Pipeline = MyRunSpace.CreatePipeline()
    MyPipeline.Commands.AddScript(scriptText)
    Dim results As Collection(Of PSObject) = MyPipeline.Invoke()
    MyRunSpace.Close()
    Dim MyStringBuilder As New StringBuilder()
    For Each obj As PSObject In results
        MyStringBuilder.AppendLine(obj.ToString())
    Next
    Return MyStringBuilder.ToString()

    End Function

I've thrown up a messagebox of the script before its passed to the RunPowershellScript function so i could make sure nothing was malformed or to ensure i wasnt doing anything stupid (i've manipulated the secure string for the purposes of this image)...

messagebox - powershellscript

The example here is just a test to see if the executor could stop the W32Time service, something that would normally require elevation. It does not work. I get an empty response back from the RunPowershellScript function and the service continues to run.

It occured to me that I'm getting a securestring from the database and then converting that securestring to a securestring, so perhaps its not ending up with the correct valid password in $credential, but from what i understand I have to provide a securestring for the password parameter of PsCredential, and without -ConvertTo-SecureString it would consider the password to just be a string. I tried this and it threw an exception about the password being null.

Can anyone help point me in the right direction?

Many thanks in advance.


Solution

  • Is the script running locally on the target or from the server?

    Credential objects are specific to the computer AND user account which creates them, so they are not transferable and can only be used on the computer which creates them.