Startup Folder Permissions in VB.NET - vb.net

I am building an app which installs all the company's bespoke software at the click of a button. Included in the list is an asset tracker/reporting tool which must run at startup for everyone. Our standard build (I have no control over this whatsoever) is Windows 10, and we also have some legacy Windows 7 machines around, but in each case the startup registry keys are locked down so I started to create shortcuts in C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup.
If the startup folder doesn't exist, then it creates the folder. Logged on as local admin, I can create the startup folder manually without UAC popping up, take ownership of the folder and assign Everyone to have modify access, creating a shortcut in there. When I log off/on, the software runs. However when I code this, I get permissions errors.
Here's the code:
Sub DetectFolders()
Dim sFolder1 As String = "C:\My Apps\"
Dim sFolder2 As String = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\"
Dim sFolder3 As String = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\"
If Not Directory.Exists(sFolder1) Then
Directory.CreateDirectory(sFolder1)
End If
If Not Directory.Exists(sFolder2) Then
Try
' create folder
TakeOwnership(sFolder3)
Application.DoEvents()
AddDirectorySecurity(sFolder3, "MyDomain\" & Environment.UserName, FileSystemRights.FullControl, AccessControlType.Allow)
Application.DoEvents()
Directory.CreateDirectory(sFolder2)
Application.DoEvents()
TakeOwnership(sFolder2)
' everyone has modify access // TEST
AddDirectorySecurity(sFolder2, "Everyone", FileSystemRights.Modify, AccessControlType.Allow)
Catch ex As Exception
MessageBox.Show(ex.Message, "Config", MessageBoxButtons.OK, MessageBoxIcon.Error)
Me.Close()
End Try
End If
End Sub
Sub TakeOwnership(ByVal sfolder As String)
' take ownership
Try
Dim ds As System.Security.AccessControl.DirectorySecurity
Dim account As System.Security.Principal.NTAccount
ds = System.IO.Directory.GetAccessControl(sfolder, System.Security.AccessControl.AccessControlSections.Owner)
account = New System.Security.Principal.NTAccount(System.Security.Principal.WindowsIdentity.GetCurrent.Name)
ds.SetOwner(account)
System.IO.Directory.SetAccessControl(sfolder, ds)
Application.DoEvents()
Catch ex As Exception
MessageBox.Show("" & ex.Message & "", "Take Ownership", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
Sub AddDirectorySecurity(ByVal FileName As String, ByVal Account As String, ByVal Rights As FileSystemRights, ByVal ControlType As AccessControlType)
Try
' Create a new DirectoryInfoobject.
Dim dInfo As New DirectoryInfo(FileName)
' Get a DirectorySecurity object that represents the
' current security settings.
Dim dSecurity As DirectorySecurity = dInfo.GetAccessControl()
' Add the FileSystemAccessRule to the security settings.
dSecurity.AddAccessRule(New FileSystemAccessRule(Account, Rights, ControlType))
' Set the new access settings.
dInfo.SetAccessControl(dSecurity)
Catch ex As Exception
MessageBox.Show("" & ex.Message & "", "Add Security", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
The first two Errors that appear is: Attempted to perform an unauthorized Operation, generated from the first time that AddDirectorySecurity and TakeOwnership are called.
I then get an error which states:
Access to the path 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup' is denied.
This is generated from the directory.createdirectory line in DetectFolders sub
I'm rapidly beginning to run out of ideas to get this working. Is it something in my code? Am I missing something, or does windows 10 not work in this way. Any constructive help will be gratefully received.

Related

Download multiple files one by one as listed in a datagridview (vb.net code)

Vb.net studio is comparivily new to (as a hobby when I have time) me so I need some help. to simply download a single file is easy I do that by the following code.
Try
' Make a WebClient.
Dim web_client As WebClient = New WebClient
' Download the file.
web_client.DownloadFile("TargetURLSite/MyFolder/" & "MyFile.exe", (Application.StartupPath & "\Plugins\" & MyFile.exe))
Catch ex As Exception
MessageBox.Show(ex.Message, "Download Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Try
Easy-Peazy...
But I now have a DataGridView column with file names of files I want to download. I thought that I could use a for each statement get the target file name from the first column of the DataGridView and execute the download function for each of the target files one by one but it dosnt seem to work I am lead to belive that its because the web client is stumbeling over its self still processing each download before being able to continue with the next.
For rowIndex = 0 To DataGridView1.RowCount - 1
'MsgBox("This cell is " & (DataGridView1.Rows(rowIndex).Cells("AppCol").Value.ToString))
Dim TargetApplication As String = (DataGridView1.Rows(rowIndex).Cells("AppCol").Value.ToString)
If My.Computer.FileSystem.FileExists(Application.StartupPath & "\Plugins\" & TargetApplication) Then
'File exists do nothing...
Else
'File dose not exist download to Plugins directory...
Try
' Make a WebClient.
Dim web_client As WebClient = New WebClient
' Download the file.
web_client.DownloadFile("TargetURLSite/MyFolder/" & TargetApplication, (Application.StartupPath & "\Plugins\" & TargetApplication))
Catch ex As Exception
MessageBox.Show(ex.Message, "Download Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
End Try
End If
Next
How do I fix this? I have a limited understanding of vb.net.

Using Impersonation for VB.NET WinForm Application, Can Save File, Can't Open File

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.

VB.NET directory.EnumerateFiles: Access to path X is denied

Ok so I am trying to write a program to find a specific file on the C drive and get its location. However the following code does not work! I've researched this a lot and gone from GetFiles to Directory.enumerateFiles. However I keep running into an exception claiming that I have no access, simply ignoring the message (closing it/pressing ok) does not continue the search and instead stops it altogether, I need a way of bypassing this if possible, so If a directory causes an exception it will skip it and move along with no error on screen if possible.
Currently the manifest file is set to "requireAdministrator" so I know thats not the issue. Running VB as administrator does not solve, and running the compiled file as admin does not work either. Hope someone can help!
Note: I'm using Visual Basic 2010 Express and have no plans to upgrade to a newever version due to hardware limitation and operating system.
Imports System.IO
Public Class GuardianScanner
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TextBox1.ReadOnly = True
TextBox1.ScrollBars = ScrollBars.Vertical
Button1.Enabled = False
TextBox1.AppendText(Environment.NewLine & "[INFO] Please wait while scanning. This can take a few minutes")
Try
For Each file As String In Directory.EnumerateFiles("C:\", "GodlyNorris.exe", SearchOption.AllDirectories)
TextBox1.AppendText(Environment.NewLine & "[INFO] Found virus: AUTONORRIS: " & file.ToString)
Next file
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
I tried posting this as a comment but it's pretty messy. This should work, basically it tries to create a directory out of every subdirectory in the C drive, and if it fails with the unauthorized access exception, it moves on to the next subdirectory.
For Each directory In New DirectoryInfo("C:\").GetDirectories()
Try
For Each file In directory.GetFiles("*", SearchOption.AllDirectories)
Try
TextBox1.Text += Environment.NewLine + file.Name
Catch ex As Exception
'MsgBox(ex.Message)
Continue For
End Try
Next
Catch ex As Exception
'MsgBox(ex.Message)
Continue For
End Try
Next

Stop using a file

first i created a file by pressing on a label then i want to be able to delete it without restarting app (because it becomes in use)
code is
Dim UniWinDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "UniWin Activator Data")
Dim MsgIODat = Path.Combine(UniWinDataPath, "MessageIO.dat")
If File.Exists(MsgIODat) Then
'ERROR HERE
File.Delete(MsgIODat)
'''''''''''''''''''''
Dim clrdata = MessageBox.Show("Data Cleared.", "", MessageBoxButtons.OK, MessageBoxIcon.Information)
ElseIf (Not (File.Exists(MsgIODat))) Then
Dim nodata = MessageBox.Show("There is no data to clear.", "", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
error: The process cannot access the file '(PATH)' because it is being used by another process.
i've searched and didn't find anyone talking about this in vb.net language
is there a way to let my app stop using it to delete the file?
You need to dispose the FileStream returned by the File.Create() Method.
When you create the file,
File.Create("file").Dispose()

How do I kill a process that doesn't have any windows in VB.net?

I am new to VB.net and I am trying to write a forms app that works with Autodesk Inventor.
Unfortunately, every time I close Inventor, the "Inventor.exe" process still stays. I didn't realize this while debugging and I only realized this when I checked the task manager after the whole system started to lag.
Killing every process with the same name is fairly simple, but the issue is that the end user might have separate documents open in another Inventor window. So I need to write a function that only kills the Inventor processes that don't have a window open.
Public Class Form1
Dim _invApp As Inventor.Application
Dim _started As Boolean = False
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Try
_invApp = Marshal.GetActiveObject("Inventor.Application")
Catch ex As Exception
Try
Dim invAppType As Type = _
GetTypeFromProgID("Inventor.Application")
_invApp = CreateInstance(invAppType)
_invApp.Visible = True
'Note: if you shut down the Inventor session that was started
'this(way) there is still an Inventor.exe running. We will use
'this Boolean to test whether or not the Inventor App will
'need to be shut down.
_started = True
Catch ex2 As Exception
MsgBox(ex2.ToString())
MsgBox("Unable to get or start Inventor")
End Try
End Try
End Sub
Another section where I start a process, which is a specific 3D model file.
Public Sub SaveAs(oTemplate As String)
'define the active document
Dim oPartDoc As PartDocument = _invApp.ActiveDocument
'create a file dialog box
Dim oFileDlg As Inventor.FileDialog = Nothing
Dim oInitialPath As String = System.IO.Path.GetFullPath("TemplatesResources\" & oTemplate)
_invApp.CreateFileDialog(oFileDlg)
'check file type and set dialog filter
If oPartDoc.DocumentType = kPartDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Part Files (*.ipt)|*.ipt"
ElseIf oPartDoc.DocumentType = kAssemblyDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Assembly Files (*.iam)|*.iam"
ElseIf oPartDoc.DocumentType = kDrawingDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Drawing Files (*.idw)|*.idw"
End If
If oPartDoc.DocumentType = kAssemblyDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Assembly Files (*.iam)|*.iam"
End If
'set the directory to open the dialog at
oFileDlg.InitialDirectory = "C:\Vault WorkSpace\Draft"
'set the file name string to use in the input box
oFileDlg.FileName = "######-AAAA-AAA-##"
'work with an error created by the user backing out of the save
oFileDlg.CancelError = True
On Error Resume Next
'specify the file dialog as a save dialog (rather than a open dialog)
oFileDlg.ShowSave()
'catch an empty string in the imput
If Err.Number <> 0 Then
MessageBox.Show("Any changes made from here will affect the original template file!", "WARNING", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
ElseIf oFileDlg.FileName <> "" Then
Dim MyFile As String = oFileDlg.FileName
'save the file
oPartDoc.SaveAs(MyFile, False)
'open the drawing document
System.Diagnostics.Process.Start(oInitialPath & ".idw")
Dim oFinalPath As String = oPartDoc.FullFileName
MessageBox.Show(oFinalPath, "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
MessageBox.Show("Loaded", "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
Dim oDrawingDoc As DrawingDocument = _invApp.Documents.ItemByName(oInitialPath & ".idw")
'oDrawingDoc.SaveAs()
End If
End Sub
Any help is appreciated.
Thanks
At the end on your form, probably FormClosed, add the following:
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
If (_invApp Is Nothing) Then Return ' if empty, then no action needed
_invApp.Quit()
Marshal.ReleaseComObject(_invApp)
_invApp = Nothing
System.GC.WaitForPendingFinalizers()
System.GC.Collect()
End Sub
This should make .NET release and properly dispose the COM Object for Inventor.
And consider declare the Inventor variable and assign Nothing/null, it's safer (avoid mistakes)
Dim _invApp As Inventor.Application = Nothing