Google Drive API v3 VB.NET Upload and Download Files to ServiceAccount - vb.net

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

Related

.NET Google API access token failing with no refresh token specified

I am trying to set up a class that can wrap around the .NET Google API so that I can use an Access Token that I have previously obtained to access a user's Google Drive. As of right now, I am just trying to get it to work so that I do not require a Refresh Token (more on that in a second). The ultimate goal is for somebody to go through a web page I have set up to authenticate where I obtain both an Access Token and a Refresh Token by directly calling to the Google Rest API (which I store in a database). They can then request to upload/download files onto their Drive on a different page which will first obtain the appropriate information from the database and then use the .NET Google API Library when accessing Drive.
However, when I attempt to access their Drive I get the the following error:
The access token has expired and could not be refreshed. Errors: refresh error, refresh error, refresh error
I know that the Access Token is valid because I obtain it only seconds earlier during my testing. Here is my code for setting up the Drive Service:
' NOTE: Code altered for brevity
Public Sub Initialize(accessToken As String)
' Set up the client secret information based on the default constants
Dim clientSecrets As New ClientSecrets()
clientSecrets.ClientId = DEFAULT_CLIENT_ID
clientSecrets.ClientSecret = DEFAULT_CLIENT_SECRET
' Set up a token based on the token data we got
' NOTE: Is it OK to leave some strings as NULL?
Dim token As New Responses.TokenResponse()
token.AccessToken = accessToken
token.RefreshToken = ""
token.TokenType = "Bearer"
token.IssuedUtc = DateTime.Now
token.ExpiresInSeconds = 3600
token.Scope = "drive"
token.IdToken = ""
' Set up a flow for the user credential
Dim init As New GoogleAuthorizationCodeFlow.Initializer()
init.ClientSecrets = clientSecrets
init.Scopes = New String() {DriveService.Scope.Drive}
init.Clock = Google.Apis.Util.SystemClock.Default
' Set up everything else and initialize the service
Dim baseInit As New BaseClientService.Initializer()
baseInit.HttpClientInitializer = New UserCredential(New GoogleAuthorizationCodeFlow(init), "user", token)
baseInit.ApplicationName = APP_NAME
_service = New DriveService(baseInit)
End Sub
Shortly after that, I then use the following code to request a folder so I can check to see if it exists or not.
Private Function GetDriveFolder(folderPath As String, ByRef folderIds As String(), Optional createMissingFolders As Boolean = False, Optional parentFolderId As String = "root") As Data.File
Dim creatingFolderPath As Boolean = False
Dim currentFolder As Data.File = Nothing
Dim folderPathSplit As String() = folderPath.Replace("/", "\").Trim("\").Split("\")
Dim folderIdList As New List(Of String)
folderIds = {}
' Loop through each folder in the path and seek each out until we reach the end
For x As Integer = 0 To folderPathSplit.Length - 1
Dim result As FileList = Nothing
If Not creatingFolderPath Then
' Build a list request which we will use to seek out the next folder
Dim request As FilesResource.ListRequest = _service.Files.List()
request.Q = "mimeType='application/vnd.google-apps.folder' and name='" & folderPathSplit(x) & "'"
If currentFolder Is Nothing Then
request.Q &= " and '" & EscapeDriveValue(parentFolderId) & "' in parents"
Else
request.Q &= " and '" & EscapeDriveValue(currentFolder.Id) & "' in parents"
End If
request.Spaces = "drive"
request.Fields = "files(id, name)"
' Execute the search, we should only get a single item back
' NOTE: Error thrown on this request
result = request.Execute()
End If
' So on.....
So, I'm just trying to get it to work with only the Access Token for the time being because if it ends up getting refreshed I'll need to know so that I can update my database. However, if I do include the Refresh Token I get the following error:
Error:"unauthorized_client", Description:"Unauthorized", Uri:""
I'm guessing this has something to do with the way I have configured my application through the Dev Console but if I authenticate through the Google API Library by having it launch a browser to get my credentials everything works fine. So, I'm really not sure where to go from here as I haven't found anybody having similar problems and the guides don't cover specifying your own Access Token.
Also, as a quick note this is the URL I am using when having the user authenticate:
String.Format("https://accounts.google.com/o/oauth2/v2/auth?client_id={0}&state={1}&redirect_uri={2}&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&include_granted_scopes=true&prompt=select_account%20consent&response_type=code", GOOGLEAPI_CLIENTID, validateState, redirectUri)
Thanks for the help!
If you have an access-token then the simplest way to create a google credential is to use the GoogleCredential.FromAccessToken() method passing in your access token.
This returns you a GoogleCredential instance which you can use to set the HttpClientInitializer property when building the DriveService.
If you then still get an error when accessing the drive service, then it's likely there's something incorrect in how you are asking for the access-token.

Downloading Azure Blobs using vb.net

Quick question about downloading Blobs from azure storage. I'm a little lost on how this works exactly, here's my code:
Try
Dim accountname As String = "macroqc"
Dim accountkey As String = My.Settings.Storagekey1
Dim creds As StorageCredentials = New StorageCredentials(accountname, accountkey)
Dim account As CloudStorageAccount = New CloudStorageAccount(creds, useHttps:=True)
Dim client = account.CreateCloudBlobClient()
Dim container As CloudBlobContainer = client.GetContainerReference("smallequipment1certifications")
Dim blob As CloudBlockBlob = container.GetBlockBlobReference(filenamelbl.Text)
Using MemoryStream = New MemoryStream()
blob.DownloadToStream(MemoryStream)
My.Settings.downloadsource = System.Text.Encoding.UTF8.GetString(MemoryStream.ToArray())
equipmentpdf.src = filenamelbl.Text
End Using
Catch ex As Exception
MessageBox.Show("Sorry an error has occured while opening your file: " & Environment.NewLine & ex.ToString, "Download Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
So I am getting an error when I run this which looks like:
So when it uploads the file to Azure storage it saves blob.Uri.AbsoluteUri & blob.Uri.AbsolutePath to the database. Am I going down the right path using this method to download the file? The Blobs are private do I need an SAS to download? I am confused and am having a really hard time finding useful documenation on this! Can someone help point me in the right direction please!!
Thanks everyone!
The reason you're getting this error is because GetBlockBlobReference expects the name of the blob (and not the full blob URL) and it creates a URL based on that. Since you're passing the complete URL, it still creates a URL but includes the blob URL. If you put a breakpoint and check the blob object's URL property, you will see something like:
https://accountname.blob.core.windows.net/containername/https://accountname.blob.core.windows.net/containername/filename
Since the blob by the name https://accountname.blob.core.windows.net/containername/filename does not exist in the container, you're getting a 404 (Not Found) error. Please provide just the file name in GetBlockBlobReference and things should work just fine.

WebClient.UploadData "The underlying connection was closed"

I'm trying to upload a file from an FTP site to Basecamp using the Basecamp API. I'm using a simple console application. Here's my code:
Try
Dim accountID As String = ConfigurationManager.AppSettings("BaseCampID")
Dim projectID As Integer = 9999999
Dim folderName As String = "XXXXX/XXXXX"
Dim fileName As String = "XXX.zip"
'The URL to access the attachment method of the API
Dim apiURL = String.Format("https://basecamp.com/{0}/api/v1/projects/{1}/attachments.json", accountID, projectID)
'Get the file from the FTP server as a byte array
Dim fileBytes As Byte() = GetFileBytes(String.Format("{0}\\{1}", folderName, fileName))
'Initialize the WebClient object
Dim client As New WebClient()
client.Headers.Add("Content-Type", "application/zip")
'Need to provide a user-agent with a URL or email address
client.Headers.Add("User-Agent", "Basecamp Upload (email#email.com)")
'Keep the connection alive so it doesn't close
client.Headers.Add("Keep-Alive", "true")
'Provide the Basecamp credentials
client.Credentials = New NetworkCredential("username", "password")
'Upload the file as a byte array to the API, and get the response
Dim responseStr As Byte() = client.UploadData(apiURL, "POST", fileBytes)
'Convert the JSON response to a BaseCampAttachment object
Dim attachment As BaseCampAttachment
attachment = JSonHelper.FromJSon(Of BaseCampAttachment)(Encoding.Default.GetString(responseStr))
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
Console.ReadLine()
End Try
But whenever it calls client.UploadData, I get the error message "The underlying connection was closed: The connection was closed unexpectedly." I ran into this issue earlier and thought I solved it by adding the "Keep-Alive" header, but it's not working anymore. The API works if I upload a local file with client.UploadFile, but I'd like to just upload the file from they byte array from the FTP rather than downloading the file locally then uploading it to Basecamp.
Any thoughts would be greatly appreciated. Thanks!
I never figured out what was wrong with the WebClient call, but I ended up using a Basecamp API wrapper from https://basecampwrapper.codeplex.com. That wrapper uses HTTPRequest and HTTPResponse instead of WebClient.UploadData. It's also much easier to just use that wrapper than to try writing my own code from scratch.

Accessing Files On Server By Specifying Credentials

Our company has a share point document server where the UNC looks something like this: \\theserver.ourdomain.com\rootdirectory
Currently this drive is mapped to the Z:\ on my local computer. To access the Z:\ you have to specify (each time you login) credentials (in our case is it our username and password we logged on with) to access the folders and files in the rootdirectory.
I am in a situation where I need to copy files onto the share point server. I want to be able to copy files onto the server without using the mapped network drive (not have to specify Z:\ in the path). How can I supply credentials so that I can perform basic IO functions like GetDirectories(), GetFiles(), IO.File.Copy() etc...?
I have looked into the following things but was unsuccessful in making them work:
LogonUser API call by specifying plain text user name and password, then taking the token from that call and impersonating that user using a new instance of the WindowsIdentity class. Was able to get the token, but the impersonation didn't seem to work. Kept getting access denied errors.
CredUIPromptForCredentials/CredUIPromptForWindowsCredentials API calls, but I realize these are just for a fancy Windows UI where you can enter your credentials into and actually don't do anything.
<DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function LogonUser(lpszUsername As String, lpszDomain As String, _
lpszPassword As String, dwLogonType As Integer, _
dwLogonProvider As Integer, ByRef phToken As IntPtr) As Boolean
End Function
<DllImport("kernel32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function CloseHandle(handle As IntPtr) As Boolean
End Function
'// logon types
Public Const LOGON32_LOGON_NETWORK As Integer = 3
Public Const LOGON32_LOGON_NEW_CREDENTIALS As Integer = 9
'// logon providers
Public Const LOGON32_PROVIDER_WINNT50 As Integer = 3
Public Const LOGON32_PROVIDER_WINNT40 As Integer = 2
Public Const LOGON32_PROVIDER_WINNT35 As Integer = 1
Public Const LOGON32_PROVIDER_DEFAULT As Integer = 0
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim token = IntPtr.Zero
Dim success = LogonUser("username", "domain", "password", _
LOGON32_LOGON_NEW_CREDENTIALS, _
LOGON32_PROVIDER_DEFAULT, token)
If Not success Then
Me.RaiseLastWin32Error()
End If
Using identity = New WindowsIdentity(token)
Using impersonated = identity.Impersonate()
Try
Dim info = New DirectoryInfo("\\theserver.ourdomain.com\rootdirectory\")
Dim files = info.GetDirectories()
Catch ex As Exception
Finally
impersonated.Undo()
End Try
If Not CloseHandle(token) Then
Me.RaiseLastWin32Error()
End If
End Using
End Using
End Sub
Private Sub RaiseLastWin32Error()
Dim hr = Marshal.GetLastWin32Error()
Dim ex = Marshal.GetExceptionForHR(hr)
If ex IsNot Nothing Then
Throw ex
End If
Throw New SystemException(String.Format("Call resulted in error code {0}", hr))
End Sub
This isn't a direct answer to your question as it is a wildly different approach. If it doesn't work for your situation sorry to bother, but have you considered using the SharePoint web services to load the files and retrieve information?
I suggest this approach for a few reasons:
The issue you are experiencing may be occurring because SharePoint implements WebDav which might not be 100% compatible with System.IO. I'm not an expert on the innards here, I don't know about the compatibility for sure, but it seems plausible.
The UNC location you have could easily be massaged into a URL that the web service requires.
You can set the credentials directly on the proxy and might have an easier time. (though we make these calls from another web server and so the app pool credentials in the example are good enough for us)
Here's some sanitized and simplified code just in case:
// location takes the form http://server.name.com/site/library/folder/document.ext
public string UploadDocument(string location, byte[] fileContents)
{
var result = String.empty;
var destination = new string[1];
destination[0] = location;
var fileName = Path.GetFileName(location);
var fieldInfo = new FieldInformation[0];
CopyResult[] copyResults;
_copyService.Url = "http://server.name.com/_vti_bin/Copy.asmx";
_copyService.Credentials = CredentialCache.DefaultCredentials;
_copyService.CopyIntoItems(fileName, destination, fieldInfo, fileContents, out copyResults);
var errorCode = copyResults[0].ErrorCode;
if (errorCode != CopyErrorCode.Success)
{
if (errorCode == CopyErrorCode.DestinationCheckedOut)
result = "File is currently checked out. Please try again later.";
else
result = "Error uploading content.";
}
return result;
}
_copyService is a dependency we inject where the run-time implementation is the proxy generated by Visual Studio tools from the Copy.asmx SharePoint web service.
You can also get folder contents and document metadata using the Lists.asmx web service. The biggest downsides to this approach are that querying the information requires some CAML knowledge and processing the results is not as easy. But the services are reasonably documented on MSDN and the operations are all working in our application.
Well, I was able to solve this with the help of the WNetAddConnection2 API. This API is used for mapping network drives as well, however you can call this method without specifying a drive letter so that it just adds the connection.
Say for example you had drive X: mapped to \\server\share
Lets also say that it requires username & password to access the files on the server. When you restart Windows 7, you will probably lose that connection (you will get a notification saying that Windows was unable to reconnect some of the network drives). If you have an application that requires access to that server's files and you attempt to access it without supplying your credentials you will get access denied exceptions. If you do a successful call to WNetAddConnection2, not only will it fix your unmapped network drive, you will also be able to access the files/directories via the System.IO namespace.
We use Sharepoint and this worked for me. Thanks to the other guys for replying also.

VB.NET (WebRequest Authentication Issue)

I'm new to WebRequest authentication and have been researching how to authenticate w/ a couple websites to pull some excel data from them. Couple things I'm confused about is
a.) how to properly read a log from Fiddler (using this to pick up get/post data from the website authentication)
b.) how do use the data from Fiddler to program the VB.NET WebRequest properly.
I've been able to authenticate w/ websites that use simple authentication HTTPS, but any site that does any redirects/REST/cookie auth I'm lost...
Let me know if I can provide anymore detail.
Dim req As Net.HttpWebRequest = Net.WebRequest.Create(Url)
If Not Login = Nothing AndAlso Not Password = Nothing Then
Dim myCache As New System.Net.CredentialCache()
myCache.Add(New Uri(Url), "Basic", New System.Net.NetworkCredential(Login, Password))
req.Credentials = myCache
End If
Dim sr As New StreamReader(req.GetResponse().GetResponseStream())
Dim ss as string = sr.ReadToEnd
'Save it as excel & close stream
sr.Close()