i'm not able to connect to the google calendar api via oauth2. I've downloaded the *.json file which includes my cliend-id and my cliend secret and use google's code example
'Copyright 2013 Google Inc
'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.
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading
Imports Google.Apis.Calendar.v3
Imports Google.Apis.Calendar.v3.Data
Imports Google.Apis.Calendar.v3.EventsResource
Imports Google.Apis.Services
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Util.Store
''' <summary>
''' An sample for the Calendar API which displays a list of calendars and events in the first calendar.
''' https://developers.google.com/google-apps/calendar/
''' </summary>
Module Program
'' Calendar scopes which is initialized on the main method.
Dim scopes As IList(Of String) = New List(Of String)()
'' Calendar service.
Dim service As CalendarService
Sub Main()
' Add the calendar specific scope to the scopes list.
Dim scopes As IList(Of String) = New List(Of String)()
scopes.Add(CalendarService.Scope.Calendar)
' Display the header and initialize the sample.
Console.WriteLine("Google.Apis.Calendar.v3 Sample")
Console.WriteLine("==============================")
dim credential As UserCredential
Using stream As New FileStream("c:/temp/client_secrets.json", FileMode.Open, FileAccess.Read)
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets, scopes, "user", CancellationToken.None,
New FileDataStore("Calendar.VB.Sample")).Result
End Using
' Create the calendar service using an initializer instance
Dim initializer As New BaseClientService.Initializer()
initializer.HttpClientInitializer = credential
initializer.ApplicationName = "VB.NET Calendar Sample"
service = New CalendarService(initializer)
' Fetch the list of calendar list
Dim list As IList(Of CalendarListEntry) = service.CalendarList.List().Execute().Items()
' Display all calendars
DisplayList(list)
For Each calendar As Data.CalendarListEntry In list
' Display calendar's events
DisplayFirstCalendarEvents(calendar)
Next
Console.WriteLine("Press any key to continue...")
Console.ReadKey()
End Sub
end class
I get an mscorelib-exception in line GoogleWebAuthorizationBroker.AuthorizeAsync(...
I cecked if the "stream" variable get the id and the secret, but everything is fine except the authentication.
Do anybody have an idea what i did wrong or what i should try?
Which "user" do i have to insert? (I used the ******#developer.gserviceaccount.com)
i also tried this version of the code for accessing the youtube api
Private Async Function GetGredentials() As Task
Try
AddToLog("Begin: GetCredentials")
'
' ClientId and ClientSecret are found in your client_secret_*****.apps.googleusercontent.com.json file downloaded from
' the Google Developers Console ( https://console.developers.google.com).
' This sample shows the ClientID and ClientSecret in the source code.
' Other samples in the sample library show how to read the Client Secrets from the client_secret_*****.apps.googleusercontent.com.json file.
'
Dim scopes As IList(Of String) = New List(Of String)()
scopes.Add(Google.Apis.Calendar.v3.CalendarService.Scope.Calendar)
OAUth2Credential = Await GoogleWebAuthorizationBroker.AuthorizeAsync( _
New ClientSecrets With {.ClientId = "242596777308-jmpe9cq9lvr23kmgt7bgsac6ogf620k8.apps.googleusercontent.com", _
.ClientSecret = "beymodgzMv9kenB8ds2R5Bb_"}, _
scopes, "me", CancellationToken.None)
AddToLog("End: GetCredentials")
If OAUth2Credential IsNot Nothing Then
If OAUth2Credential.Token IsNot Nothing Then
AddToLog(String.Concat("Token Issued: ", OAUth2Credential.Token.Issued))
End If
End If
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical, "Google Authorization")
End
End Try
End Function
the used id and secret are copied directly from google's api console. The code throws now an exception "application not found"
The exception stacktrace is:
bei Microsoft.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
bei Microsoft.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccess(Task task)
bei Microsoft.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
bei Microsoft.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
bei YouTube_API_V3_Demo.Main.VB$StateMachine_1_GetGredentials.MoveNext() in C:\Users\Michael\Downloads\Sample\YouTube_API_V3_Demo\YouTube_API_V3_Demo\Main.vb:Zeile 202.
I think that something is wrong with the oauth2 authentication, but what??
What do i have to insert in the "user" parameter?
Michael
Related
How to retrieve all template from Docusign api using vb.net.I manage to create multiple template through docusing website, and need to retrieve list of template from account.
Regards,
Aravind
You will need to add the nuget package from https://www.nuget.org/packages/DocuSign.eSign.dll/ but this code is not for latest version.
Imports System
Imports System.Collections.Generic
Imports DocuSign.eSign.Api
Imports DocuSign.eSign.Client
Imports DocuSign.eSign.Model
Imports Microsoft.AspNetCore.MvcPrivate Sub SurroundingSub()
Dim config = New Configuration(New ApiClient(basePath))
config.AddDefaultHeader("Authorization", "Bearer " & accessToken)
Dim templatesApi As TemplatesApi = New TemplatesApi(config)
Dim options As TemplatesApi.ListTemplatesOptions = New TemplatesApi.ListTemplatesOptions()
options.searchText = "Example Signer and CC template"
Dim results As EnvelopeTemplateResults = templatesApi.ListTemplates(accountId, options)
End Sub
I'm working on adding a document upload function to an application I've written. I want the user to be able to upload, open, and delete a document on a network drive that they cannot access normally. With this in mind, I stumbled upon Impersonation, where the user can impersonate a user account that has full rights to the drive, then dispose of that after the code has been executed.
I've never used impersonation before, so during my research I found this thread:
Impersonate a Windows or Active Directory user from a different, untrusted domain
I created and copied the class that user Max Vernon had posted as follows:
Option Explicit On
Option Infer Off
Imports System
Imports System.Runtime.InteropServices ' DLL Import
Imports System.Security.Principal ' WindowsImpersonationContext
Imports System.ComponentModel
Public Class Impersonation
'Group Type Enum
Enum SECURITY_IMPERSONATION_LEVEL As Int32
SecurityAnonymous = 0
SecurityIdentification = 1
SecurityImpersonation = 2
SecurityDelegation = 3
End Enum
Public Enum LogonType As Integer
'This logon type is intended for users who will be interactively using the computer, such as a user being logged on
'by a terminal server, remote shell, or similar process.
'This logon type has the additional expense of caching logon information for disconnected operations,
'therefore, it is inappropriate for some client/server applications, such as a mail server.
LOGON32_LOGON_INTERACTIVE = 2
'This logon type is intended for high performance servers to authenticate plaintext passwords.
'The LogonUser function does not cache credentials for this logon type.
LOGON32_LOGON_NETWORK = 3
'This logon type is intended for batch servers, where processes may be executing on behalf of a user without
'their direct intervention. This type is also for higher performance servers that process many plaintext
'authentication attempts at a time, such as mail or Web servers.
'The LogonUser function does not cache credentials for this logon type.
LOGON32_LOGON_BATCH = 4
'Indicates a service-type logon. The account provided must have the service privilege enabled.
LOGON32_LOGON_SERVICE = 5
'This logon type is for GINA DLLs that log on users who will be interactively using the computer.
'This logon type can generate a unique audit record that shows when the workstation was unlocked.
LOGON32_LOGON_UNLOCK = 7
'This logon type preserves the name and password in the authentication package, which allows the server to make
'connections to other network servers while impersonating the client. A server can accept plaintext credentials
'from a client, call LogonUser, verify that the user can access the system across the network, and still
'communicate with other servers.
'NOTE: Windows NT: This value is not supported.
LOGON32_LOGON_NETWORK_CLEARTEXT = 8
'This logon type allows the caller to clone its current token and specify new credentials for outbound connections.
'The new logon session has the same local identifier but uses different credentials for other network connections.
'NOTE: This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider.
'NOTE: Windows NT: This value is not supported.
LOGON32_LOGON_NEW_CREDENTIALS = 9
End Enum
Public Enum LogonProvider As Integer
'Use the standard logon provider for the system.
'The default security provider is negotiate, unless you pass NULL for the domain name and the user name
'is not in UPN format. In this case, the default provider is NTLM.
'NOTE: Windows 2000/NT: The default security provider is NTLM.
LOGON32_PROVIDER_DEFAULT = 0
LOGON32_PROVIDER_WINNT35 = 1
LOGON32_PROVIDER_WINNT40 = 2
LOGON32_PROVIDER_WINNT50 = 3
End Enum
'Obtains user token.
Declare Auto Function LogonUser Lib "advapi32.dll" (ByVal lpszUsername As String, ByVal lpszDomain As String, ByVal lpszPassword As String, ByVal dwLogonType As LogonType, ByVal dwLogonProvider As LogonProvider, ByRef phToken As IntPtr) As Integer
'Closes open handles returned by LogonUser.
Declare Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean
'Creates duplicate token handle.
Declare Auto Function DuplicateToken Lib "advapi32.dll" (ExistingTokenHandle As IntPtr, SECURITY_IMPERSONATION_LEVEL As Int16, ByRef DuplicateTokenHandle As IntPtr) As Boolean
'WindowsImpersonationContext newUser.
Private newUser As WindowsImpersonationContext
'Attempts to impersonate a user. If successful, returns
'a WindowsImpersonationContext of the new user's identity.
'
'Username that you want to impersonate.
'Logon domain.
'User's password to logon with.
Public Sub Impersonator(ByVal sDomain As String, ByVal sUsername As String, ByVal sPassword As String)
'Initialize tokens
Dim pExistingTokenHandle As New IntPtr(0)
Dim pDuplicateTokenHandle As New IntPtr(0)
If sDomain = "" Then
sDomain = System.Environment.MachineName
End If
Try
Const LOGON32_PROVIDER_DEFAULT As Int32 = 0
Const LOGON32_LOGON_NEW_CREDENTIALS = 9
Dim bImpersonated As Boolean = LogonUser(sUsername, sDomain, sPassword, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, pExistingTokenHandle)
If bImpersonated = False Then
Dim nErrorCode As Int32 = Marshal.GetLastWin32Error()
Throw New ApplicationException("LogonUser() failed with error code: " & nErrorCode.ToString)
End If
Dim bRetVal As Boolean = DuplicateToken(pExistingTokenHandle, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, pDuplicateTokenHandle)
If bRetVal = False Then
Dim nErrorCode As Int32 = Marshal.GetLastWin32Error
CloseHandle(pExistingTokenHandle)
Throw New ApplicationException("DuplicateToken() failed with error code: " & nErrorCode)
Else
Dim newId As New WindowsIdentity(pDuplicateTokenHandle)
Dim impersonatedUser As WindowsImpersonationContext = newId.Impersonate
newUser = impersonatedUser
End If
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
Finally
If pExistingTokenHandle <> IntPtr.Zero Then
CloseHandle(pExistingTokenHandle)
End If
If pDuplicateTokenHandle <> IntPtr.Zero Then
CloseHandle(pDuplicateTokenHandle)
End If
End Try
End Sub
Public Sub Undo()
newUser.Undo()
End Sub
End Class
The impersonation works great for "uploading" (actually just copying a file over from the users local files to the network drive, creating a specific file path if it doesn't exist) but doesn't seem to work when trying to open the file back up, or delete said file.
I get an access denied error like this:
Error Message When Trying to Open File
The Open File Click Event and Class Call Looks Like This:
Private Sub btnOpenDoc_Click(sender As Object, e As EventArgs) Handles btnOpenDoc.Click
Dim Impersonator As New Impersonation
Dim sUser As String = "UserNameGoesHere"
Dim sPass As String = "PasswordGoesHere"
Dim sDomain As String = "DomainGoesHere"
Try
If sActionID <> "" And iDocument = 1 Then
'Starts impersonation
Impersonator.Impersonator(sDomain, sUser, sPass)
Process.Start(RetrieveFilePath())
'Ends Impersonation
Impersonator.Undo()
End If
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
modGlobal.WriteToErrorLog(ex.Message, "frmActionEntry", modGlobal.GetExceptionInfo(ex), "frmActionEntry->btnOpenDoc_Click", currentUser.getEmployeeName())
End Try
End Sub
Here's Document Delete Function:
Private Function DeleteFile() As Boolean
Dim Impersonator As New Impersonation
Dim sUser As String = "UsernameGoesHere"
Dim sPass As String = "PasswordGoesHere"
Dim sDomain As String = "DomainGoesHere"
Try
'Starts impersonation
Impersonator.Impersonator(sDomain, sUser, sPass)
File.Delete(RetrieveFilePath())
Return True
'Ends Impersonation
Impersonator.Undo()
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
modGlobal.WriteToErrorLog(ex.Message, "frmActionEntry", modGlobal.GetExceptionInfo(ex), "frmActionEntry->DeleteFile", currentUser.getEmployeeName())
Return False
End Try
End Function
It's used in basically the same way in the FileSave function. Like I said I'm new to impersonation, and feel like I've hit a wall, having researched and tried various things all morning. Any advice is much appreciated!
-Levi
So after much research and trial and error I have an answer to this.
The short answer:
There is not a clean, elegant way to use impersonation to open a file on a network drive because you either butt heads with Windows Security or run into problems with Windows Shell. I decided to go another route.
The long answer:
I believe I was correct in that the Access was Denied error was due to trying to open a file as the impersonated user on the local user's computer. To get around this I decided to try and use ProcessStartInfo() to pass in the correct credentials (while also using impersonation to access the drive) like this:
'Opens the document associated with this action
Private Sub btnOpenDoc_Click(sender As Object, e As EventArgs) Handles btnOpenDoc.Click
'Initializes an impersonation object
Dim Impersonator As New Impersonation
'Strings with login credentials
Dim sUser As String = "UsernameGoesHere"
Dim sPass As String = "PasswordGoesHere"
Dim sDomain As String = "DomainGoesHere"
'Used to load file path in from RetrieveFilePath()
Dim sPath As String = ""
Try
If sActionID <> "" And iDocument = 1 Then
'Starts impersonation
Impersonator.Impersonator(sDomain, sUser, sPass)
'Initializes a ProcessStartInfo Object to use with impersonation
'as Process.Start class always inherits the security context of
'the parent process i.e. the local user
Dim startInfo As New ProcessStartInfo()
'Creates a secure string as the startInfo.Password parameter only accepts SecureStrings
Dim securePass As New Security.SecureString()
'You can't put a full string into a SecureString, so appending char by char
For Each c As Char In sPass
securePass.AppendChar(c)
Next
'Grab the file path
sPath = RetrieveFilePath()
'Load in the parameters for startInfo
startInfo.FileName = sPath
startInfo.UserName = sUser
startInfo.Password = securePass
startInfo.Domain = sDomain
startInfo.UseShellExecute = False
startInfo.WorkingDirectory = "\\Directory\Goes Here"
If File.Exists(sPath) Then
'Execute the process using startInfo
Process.Start(startInfo)
Else
MsgBox("File Not Found!")
End If
'Dispose of securePass
securePass.Dispose()
'Ends Impersonation
Impersonator.Undo()
End If
Catch ex As Exception
MessageBox.Show("An error has occurred. Please contact Technical Support. " & vbCrLf & ex.Message, "Application Title", MessageBoxButtons.OK, MessageBoxIcon.Error)
modGlobal.WriteToErrorLog(ex.Message, "frmActionEntry", modGlobal.GetExceptionInfo(ex), "frmActionEntry->btnOpenDoc_Click", currentUser.getEmployeeName())
End Try
End Sub
There are some interesting aspects to note here. You have to use a SecureString for the password when using ProcessStartInfo, and can only be assigned per character, and more importantly, I had to set UseShellExecute property to False.
I was hopeful that this would work, but after some iterations I got stuck on this error message:
Error Message Example
I figured out that this was due to being unable to access Windows Shell to find the default program to open the corresponding file type with, so it just expected an executable. After more research I was unable to find a clean way to get around this so I've decided to go about addressing this file upload a different way.
I know this is an old question, but maybe it can help someone else, I had the same problem and finally, realized that it's not enough to grant access to the impersonate user to read and write on the folder but also modify, if you don't, the user can not delete the file.
I've been trying to follow https://developers.google.com/drive/v3/web/quickstart/dotnet and the Google API documentation, as well as searching all over the internet, but there really isn't a straightforward example to use (especially for v3)
I have a VB.NET GUI that contains a listview with the names of all plain text files in a folder. Clicking on one will display its' contents in a textbox. You can also type into a blank text box and save it. I want to allow multiple users to upload their text file to the Google Drive and be able to download all text files that are stored there.
I don't have much of an issue translating code from C# to VB.NET, and I think I'm fine with authenticating the service account with Google (or at least I don't get an error), but uploading only shows me response = Nothing. Any help is appreciated.
I created the service account through Google and have the following:
Dim service = AuthenticateServiceAccount("xxxxx#xxxxx.iam.gserviceaccount.com", "C:\Users\xxxxx\Documents\Visual Studio 2015\Projects\MyProject\accountkey-0c1aa839896b.json")
If drive.UploadFile(service, "C:\Users\xxxxx\Documents\Visual Studio 2015\Projects\MyProject\file.txt") Is Nothing Then
MsgBox("File not uploaded")
Else
MsgBox("File uploaded")
End If
Authenticate:
Public Function AuthenticateServiceAccount(ByVal serviceAccountEmail As String, ByVal serviceAccountCredentialFilePath As String) As DriveService
Dim scopes As String() = {DriveService.Scope.Drive, DriveService.Scope.DriveAppdata, DriveService.Scope.DriveReadonly, DriveService.Scope.DriveFile, DriveService.Scope.DriveMetadataReadonly, DriveService.Scope.DriveReadonly, DriveService.Scope.DriveScripts}
Try
If (String.IsNullOrEmpty(serviceAccountCredentialFilePath)) Then
Throw New Exception("Path to the service account credentials file is required.")
End If
If Not IO.File.Exists(serviceAccountCredentialFilePath) Then
Throw New Exception("The service account credentials file does not exist at: " + serviceAccountCredentialFilePath)
End If
If (String.IsNullOrEmpty(serviceAccountEmail)) Then
Throw New Exception("ServiceAccountEmail is required.")
End If
If (Path.GetExtension(serviceAccountCredentialFilePath).ToLower() = ".json") Then
Dim credential As GoogleCredential
Dim sstream As New FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read)
credential = GoogleCredential.FromStream(sstream)
credential.CreateScoped(scopes)
'Create the Analytics service.
Return New DriveService(New BaseClientService.Initializer() With {
.HttpClientInitializer = credential,
.ApplicationName = "Drive Service Account Authentication Sample"
})
Else
Throw New Exception("Unsupported Service accounts credentials.")
End If
Catch e As Exception
MsgBox("Create service account DriveService failed" + e.Message)
Throw New Exception("CreateServiceAccountDriveFailed", e)
End Try
End Function
Upload File:
Public Function UploadFile(service As DriveService, FilePath As String) As Google.Apis.Drive.v3.Data.File
If (System.IO.File.Exists(FilePath)) Then
Dim body As New Google.Apis.Drive.v3.Data.File()
body.Name = System.IO.Path.GetFileName(FilePath)
body.Description = "Text file"
body.MimeType = "text/plain"
'files content
Dim byteArray As Byte() = System.IO.File.ReadAllBytes(FilePath)
Dim stream As New System.IO.MemoryStream(byteArray)
Try
Dim request As FilesResource.CreateMediaUpload = service.Files.Create(body, stream, "text/plain")
request.Upload()
Return request.ResponseBody
Catch e As Exception
MsgBox("An error occurred: " + e.Message)
Return Nothing
End Try
Else
MsgBox("File does not exist: " + FilePath)
Return Nothing
End If
End Function
As stated here, since you are using a Service Account, all the folders and files will be created in this Service Account's Drive which cannot be accessed through a web UI and will be limited to the default quota.
To add content in a user's Drive, you will need to go through the regular OAuth 2.0 flow to retrieve credentials from this user. You can find more information about OAuth 2.0 on this pages:
Retrieve and use OAuth 2.0 credentials.
Quickstart: it has a quickstart sample in C# that you could use.
Using OAuth 2.0 to access Google APIs
You may also check this related thread: upload files to Google drive in VB.NET - searching for working code
I'm currently building a program which requires numerous staff to use my program and the program is located on a shared drive on the network so all users can access the program. In my program I have a Database which I use to manage all the user accounts and other information. When you run the program for the first time ever on the network, it asks the administrator where to create the database. After the program creates the database, I save the connection string to a string variable in a class module within my program. However once I exit the program the value I set the to the string variable in the class gets erased. Is there a way to prevent the string from losing its value after closing the program ? I know I could do this via my.settings but I don't want to do it that way.
You can make your own settings file using a binary serializer.
This method can be used to store an instance of your settings class to a file, which is not very human-readable. If human readability and editability is required, you could use an xml serializer instead. The settings file will reside in the application directory. You can control this with the variable settingsFileName.
Create a new console application and paste the code below. Run it a couple of times and note that the "connection string" is persisted through application close and open.
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Module Module1
Private settingsFileName As String = "Settings.bin"
Private mySettingsClass As SettingsClass
Private Sub loadSettings()
Dim formatter As New BinaryFormatter()
If File.Exists(settingsFileName) Then
Using stream As New FileStream(settingsFileName, FileMode.Open)
mySettingsClass = CType(formatter.Deserialize(stream), SettingsClass)
End Using
Else
Using Stream As New FileStream(settingsFileName, FileMode.CreateNew)
mySettingsClass = New SettingsClass()
formatter.Serialize(Stream, mySettingsClass)
End Using
End If
End Sub
Private Sub saveSettings()
Dim formatter As New BinaryFormatter()
If File.Exists(settingsFileName) Then
Using stream As New FileStream(settingsFileName, FileMode.Truncate)
formatter.Serialize(stream, mySettingsClass)
End Using
Else
Using stream As New FileStream(settingsFileName, FileMode.CreateNew)
formatter.Serialize(stream, mySettingsClass)
End Using
End If
End Sub
<Serializable>
Public Class SettingsClass
Public Property ConnectionString As String = ""
End Class
Sub Main()
Console.WriteLine("Loading settings...")
loadSettings()
Dim connectionString = mySettingsClass.ConnectionString
Console.WriteLine("Connection string: ""{0}""", connectionString)
Console.WriteLine("Enter new connection string...")
mySettingsClass.ConnectionString = Console.ReadLine()
Console.WriteLine("Saving settings...")
saveSettings()
Console.WriteLine("Done!")
Console.ReadLine()
End Sub
End Module
Add additional properties to SettingsClass which can be used elsewhere in your application.
The company I'm working in has a web service that can create new Active Directory accounts based on information that is typed in e.g. username, firstname, lastname, OU1, OU2, etc.
This web service has been working fine on Windows Server 2003. Now I'm working on moving the web service to 2008 R2 servers. However a certain functionality doesn't work anymore, which is when it tries to set a random initial password to the newly created account.
The exception that is thrown is a TargetInvocationException containing an inner exception of COMException with a message of "The RPC Server is unavailable".
Imports System.DirectoryServices
Dim ldapPath As String = "..." 'assume valid LDAP path
Dim objContainer As DirectoryEntry = New DirectoryEntry(ldapPath, vbNullString, vbNullString, AuthenticationTypes.Secure)
objUser = objContainer.Children.Add("cn=" & Username.Trim, "user")
'sets objUser.Properties e.g. givenName, displayName, userPrincipalName, samAccountName, etc. Not important...
objUser.CommitChanges()
strPassword = RandomPassword() 'function that generates random password
objUser.Invoke("SetPassword", New Object() {strPassword})
objUser.Properties("useraccountcontrol").Value = "512" ' set as normal account
objUser.CommitChanges()
'and so on...
The error happens on the line that says:
objUser.Invoke("SetPassword", New Object() {strPassword})
Strangely the account creation itself works and I can see the new user from Active Directory Users and Computers.
I have consulted a few different people who manage the security of the DCs and the web servers, they don't really know why...
In the end I figured out a different way of setting the password, which is using the System.DirectoryServices.AccountManagement library.
So the code becomes something like this:
Imports System.DirectoryServices
Imports System.DirectoryServices.AccountManagement
Dim ldapPath As String = "..." 'assume valid LDAP path
Dim objContainer As DirectoryEntry = New DirectoryEntry(ldapPath, vbNullString, vbNullString, AuthenticationTypes.Secure)
Dim objUser As DirectoryEntry = objContainer.Children.Add("cn=" & Username.Trim, "user")
'sets objUser.Properties e.g. givenName, displayName, userPrincipalName, samAccountName, etc. Not important...
objUser.CommitChanges()
Dim domainContext As PrincipalContext = New PrincipalContext(ContextType.Domain, ...)
Dim user As UserPrincipal = UserPrincipal.FindByIdentity(domainContext, IdentityType.SamAccountName, Trim(Username))
strPassword = RandomPassword() 'function that generates random password
user.SetPassword(strPassword)
user.Enabled = True 'setting these two properties
user.PasswordNotRequired = False 'result in useraccountcontrol value = 512 (normal account)
user.Save()
'and so on...
I'm mixing old code with new code in this one, but it appears to be working really well on the new servers. One thing to note is that sometimes the UserPrincipal.FindByIdentity call initially returns Nothing, I believe due to delay in the account creation or with the replication. So I have to make sure that the user object is not Nothing by trying FindByIdentity multiple times until I get the object.
Despite finding a solution (more like a way around) to the problem, still I'm confused as to why the old code does not work on the new servers, but the new one does. The error is very generic and searching the internet for clues have resulted in nothing but more confusion.
Would really appreciate if experts out there can shed some lights or even comment on the way my new code looks, any problems?
Thanks in advance.