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)...
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.
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.
Related
I need to change the Passwords of an Active Directory account in windows 10 in VB.NET.
The program, I wrote, runs as local administrator,
My working code with a valid user account is (Domain_xps and UserName_xps are Strings and pwdPtr System.Runtime.InteropServices.Marshal.SecureStringToBSTR of a SecureString):
dEntry = New DirectoryServices.DirectoryEntry("LDAP://" & Domain_xps, UserName_xps
, System.Runtime.InteropServices.Marshal.PtrToStringBSTR(pwdPtr)
, System.DirectoryServices.AuthenticationTypes.Secure
+ System.DirectoryServices.AuthenticationTypes.Sealing
+ System.DirectoryServices.AuthenticationTypes.ServerBind) ',pwd)
nativeObject = dEntry.NativeObject
Dim searcher_Fullname_xpo As System.DirectoryServices.DirectorySearcher
= New System.DirectoryServices.DirectorySearcher(dEntry)
With searcher_Fullname_xpo
.Filter = "(&(objectClass=User) (sAMAccountName=" & UserName_xps & "))"
End With
result_xpo = searcher_Fullname_xpo.FindOne
Dim user As DirectoryServices.DirectoryEntry 'open directory
user = result_xpo.GetDirectoryEntry() 'get directory results
user.Username = UserName_xps
user.Password = PWD_xps
user.Path = result_xpo.GetDirectoryEntry().Path
user.AuthenticationType = System.DirectoryServices.AuthenticationTypes.Secure
+ System.DirectoryServices.AuthenticationTypes.Sealing
+ System.DirectoryServices.AuthenticationTypes.ServerBind
user.Options.PasswordPort = 389
user.Options.PasswordEncoding = 1
user.Invoke("ChangePassword", New Object() {PWD_xps, PWDNeu_xps})
user.CommitChanges() 'commit changes
user.Close() 'close directory
But if a account has expired through holidays or if a new user with a one time password is generated and tries to change his password, i get an error.
The user or password are wrong.
while debugging i noticed, that following lines produce the same error.
nativeObject = dEntry.NativeObject
Dim searcher_Fullname_xpo As System.DirectoryServices.DirectorySearcher = New System.DirectoryServices.DirectorySearcher(dEntry)
With searcher_Fullname_xpo
.Filter = "(&(objectClass=User) (sAMAccountName=" & UserName_xps & "))"
End With
result_xpo = searcher_Fullname_xpo.FindOne
And also
user.Options.PasswordPort = 389
user.Options.PasswordEncoding = 1
user.Invoke("ChangePassword", New Object() {PWD_xps, PWDNeu_xps})
each of these produce the error and doesn't change the password correctly, after encapsulating every line in a try catch expression.
Active Directory shows a change, but the user account is not valid any more.
I tried basically the same methods, in hope that i could set the options.
Dim ADS_OPTION_PASSWORD_PORTNUMBER As Long = 6
Dim ADS_OPTION_PASSWORD_METHOD As Long = 7
Dim ADS_PASSWORD_ENCODE_REQUIRE_SSL As Integer = 0
Dim ADS_PASSWORD_ENCODE_CLEAR As Integer = 1
Try
user.Invoke("SetOption", New Object() {ADS_OPTION_PASSWORD_PORTNUMBER, 389})
Catch ex3 As Exception
End Try
Try
user.Invoke("SetOption", New Object() {ADS_OPTION_PASSWORD_METHOD, ADS_PASSWORD_ENCODE_CLEAR})
Catch ex3 As Exception
End Try
And i tried also
user.Invoke("SetPassword", New Object() {PWDNeu_xps})
the error message stays the same
To set the password seems the right way, but as i can't set the password port or enable the password method, it produces the same error.
i also found this old thread How to change password in active directory when password expired
but that is not longer possible under windows 10.
How can i change the password, of an expired account with the needed options in VB Net or can i configure the account, so that it is possible to achieve it.
So an update on this situation. 28.10.2021
I also tried as workaround a powershell command as described in Microsoft or here for that matter.
And the same thing happens, the password change isn't possible,
Set-ADAccountPassword : The server has rejected the client credentials.
With a valid user the comand works as does it in DotNet
Write a program in a 32 bit version of C or up to C++11 on a 32 bit version of Microsoft Windows up to XP pro sp2 (and not later version) which does what you want to do. Compile it with Code::Blocks up to version 17.12 and not a later version. Do not allow any version of Visual C++ or Visual Basic or Visual Studio on that computer before or during this process.
Load and run that program, on your Windows 10 computer, as the highest administrator level that Windows 10 will "allow". If you cannot get it to run compiled as GUI, then try to get it to run compiled as CLI. Remember, This is Extremely important: Compile the program as a stand-alone single executable that uses NO external dlls.
Change the password outside of any Windows 10 commands. Edit the file that they are in with your program, and do not tell Windows 10 anything is happening.
I'm trying to fetch the files inside a folder ordered by LastWriteTime.
The code is running very fast when accessing to a local path (C:\MyFolder), but is hanging when accessing to a remote path (\\MyServer\MyFolder)
Dim myOrderedList As List(Of String) = (From item In IO.Directory.GetFiles(strFolderSource) _
Let file = New IO.FileInfo(item) _
Order By file.LastWriteTime _
Select item).ToList()
Should this code work? Is not allowed this method to get files from a remote folder?
Which alternative code could I use to get the same result without hanging?
EDITED (2019-01-18 16:32):
Sorry guys, I've tried the proposed solution from Rango, and still the same hang. Finally I created a small logging system to catch the step that caused the problem, and realized that all is a credential problem.
Just before the code I posted I do a NET USE to grant access to the remote computer, and the net use is executed, but for any reason, the GetFiles() fails because of Logon failure: unknown user name or bad password.
So, Could I ensure the credentials with the net use before call the GetFiles()?
Maybe using a pause or something like this?
FULL CODE:
Dim processInfo As New System.Diagnostics.ProcessStartInfo()
processInfo.FileName = "C:\WINDOWS\system32\net"
processInfo.Arguments = "net use \\MyServer\IPC$ ""password"" /USER:Username"
System.Diagnostics.Process.Start(processInfo)
Dim myOrderedList As List(Of String) = (From item In IO.Directory.GetFiles("\\MyServer\g$\MyFolder") _
Let file = New IO.FileInfo(item) _
Order By file.LastWriteTime _
Select item).ToList()
You could try to use DirectoryInfo.EnumerateFiles instead wich has two advantages:
No consecutive security handshakes from the remote server necessary
Streaming the files instead of loading all into memory before you start ordering them
Dim di = new DirectoryInfo(strFolderSource)
Dim files = From fi In di.EnumerateFiles() Order By fi.LastWriteTime Select fi.FullName
Dim myOrderedList As List(Of String) = files.ToList()
Finally solved including a sleep of 5 seconds after the net use and before the GetFiles():
System.Threading.Thread.Sleep(5000)
Thanks for your time, and hope this helps anybody with a similar problem.
As the title implies, I'm looking for a way to pull specific file(s) from a private GitLab repo using VB.net (2017).
I have an application that I'm writing which will call certain PowerShell scripts. I have a few other users working with me writing the scripts, so we are using GitLab as the repository for these.
We want the application to pull the latest version of the scripts from GitLab when the application opens, then from within the app, call the scripts.
I have everything done, with the exception of downloading the scripts from GitLab.
So I'm posting an answer here just in case anyone else has the same question. I was actually able to get this done pretty easily.
First, you have to generate a private token. Plenty of walk-throughs for that, so I won't go into that here.
Next, you have to get the address of raw file that you want to download. You can get this by opening the file in GitLab, then there's a button on the top right of the window to "Open Raw", which opens the raw page.
See Image Here
Grab the url from the address bar. Once you have that, you have all the pieces you need to curl the file down using VB.net.
You have to take the address of the raw file, let's say that was "https://gitlab.com/CompanyName/raw/master/folder/filename.ps1", you then append ?, with your private token so it looks like this: "https://gitlab.com/CompanyName/raw/master/folder/filename.ps1?private_token=MyPrivateToken" and use a curl (through powershell) to get it.
I already had a function in my code to run powershell scripts (with code I believe I got off this site...forgot the exact location), which was like this:
Private Function RunScript(ByVal scriptText As String) As String
' Takes script text as input and runs it, then converts
' the results to a string to return to the user
' create Powershell runspace
Dim MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()
' open it
MyRunSpace.Open()
' create a pipeline and feed it the script text
Dim MyPipeline As Pipeline = MyRunSpace.CreatePipeline()
MyPipeline.Commands.AddScript(scriptText)
' add an extra command to transform the script output objects into nicely formatted strings
' remove this line to get the actual objects that the script returns. For example, the script
' "Get-Process" returns a collection of System.Diagnostics.Process instances.
MyPipeline.Commands.Add("Out-String")
' execute the script
Dim results As Collection(Of PSObject) = MyPipeline.Invoke()
' close the runspace
MyRunSpace.Close()
' convert the script result into a single string
Dim MyStringBuilder As New StringBuilder()
For Each obj As PSObject In results
MyStringBuilder.AppendLine(obj.ToString())
Next
' return the results of the script that has
' now been converted to text
Return MyStringBuilder.ToString()
End Function
Now, I can call a curl command with that function like this:
RunScript("curl https://gitlab.com/CompanyName/raw/master/folder/filename.ps1?private_token=MyPrivateToken -outfile C:\DownloadFolder\FileName.ps1")
That's it! Anytime you need to get a file, you can simply get the location of the raw file and modify the function call to reflect the new address and grab it.
I am developing a VB.NET update system for a volunteer organisation’s MS Access database. The database is protected by a password as it contains personal information. I have created the application using the VB designer. I need to be able to code the application so that, if the owner decides to change the MS Access password, they will have no need to come back to me to change the code and rebuild the solution. In other words, I do not want the password to be hard coded in the app.config file or the settings.designer.vb file. My code should not need to know the password as a simple call to one of the Fill functions can test any password entered by the user. My problem is that I have found no way to alter the connection string that is tested in the setttings.designer.vb code whenever the database is accessed. I am using Visual Studio 2017.
I have spent a long time searching the web for answers and have tried various solutions involving the configurationmanager without success. I am new to this area so I would be most grateful if anyone here can help.
Here is my latest attempt which still produces an invalid password error even though the third debug statement suggests that the connection string, including the password, has been correctly set.
Public Sub UpdateConnString(connString As String)
Dim configFileMap As New ExeConfigurationFileMap()
Dim config As Configuration = ConfigurationManager.OpenExeConfiguration(configFileMap.ExeConfigFilename)
Dim connStringName As String = "TestConnectionString"
Debug.Print("0 " + config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString)
config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString = connString
Debug.Print("1 " + config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString)
config.Save(ConfigurationSaveMode.Modified, True)
Debug.Print("2 " + config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString)
End Sub
Just because a connection string is stored in the config file, you aren't required to use it as it is. You can read in that default value and then edit it before using it, e.g.
Dim builder As New OleDbConnectionStringBuilder(My.Settings.DefaultConnectionString)
builder.DataSource = dataSource
Dim connectionString = builder.ConnectionString
You can add or modify any part of a connection string you want that way at run time.
Thank you for your response. Unfortunately, the code throws a compilation error - "DefaultConnectionString is not a member of My.Settings".
Fortunatley I have now managed to find a working solution:
'My.Settings.Item("TestConnectionString") = connectionString
I have the following function in an Excel sheet module, which sends SQL queries to SAS:
Function run_query(query)
Dim app ' application
Dim project ' Project object
Dim sasProgram ' Code object (SAS program)
Set app = CreateObject("SASEGObjectModel.Application.5.1")
Set project = app.New
Set sasProgram = project.CodeCollection.Add
sasProgram.Text = "PROC SQL;" + query + " QUIT;"
sasProgram.Run
app.Quit
End Function
It sometimes works like a charm, and most often doesn't, as it asks for my credentials (triggered by command sasProgram.Run as it's where the debugger stops), I didn't find a way to make the error, or success, reproducible.
I've set my credentials persistence to Persist for user but I still have issues.
I've also tried to set my Autentification to None (attempt anonymous connection) and Windows integrated (Uses your current windows account) and none of them changed the situation.