Problem:
I have a class where I have a eventlog that logs for that particular instance of the class and logs to a shared eventlog and to track changes across all instances.
My issue is that I want the logs to be re sizable in their number of entries and I need to make sure that the shared log is never smaller than any of the instanced logs. Is it possible to check this?
What I've tried:
I've seen a-lot about GetType and reflections and I've been able to get instance names but I can't figure out how to reference data within those instances.
Below is a psuedocode of where I am at.
Public Class Test
Public InstancedLog As DataSet
Public Shared SharedLog As DataSet
Public Shared Sub ResizeSharedLog(ByRef DesiredSize As Integer)
If DesiredSize < ("check and sum up the size of all instances of InstancedLog") Then
'can't resize, too small.
Else
'resize to DesiredSize.
End If
End Sub
End Class
Any help would be much appreciated.
Here's an example of a class that tracks its own instances:
Public Class Class1
Implements IDisposable
Private Shared instances As New List(Of Class1)
Public Sub New()
instances.Add(Me)
End Sub
Public Shared Sub TouchEveryInstance()
For Each instance As Class1 In instances
'Use instance here.
Next
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
instances.Remove(Me)
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
Dispose(True)
' TODO: uncomment the following line if Finalize() is overridden above.
' GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Most of that code is auto-generated by VS when you add the Implements IDisposable line. The entire region was added automatically except the line that refers to the instances field. I added the instances field, the constructor and the TouchEveryInstance method.
I need some help with IPlugin implementation for my app.
PluginContracts code:
Public Interface IPlugin
ReadOnly Property Name() As String
Sub DoSomething()
Sub DoExit()
End Interface
Main App:
Imports PluginContracts
Module Program
Sub Main()
LoadTray()
End Sub
Sub LoadTray()
Dim dll As Assembly = Assembly.LoadFrom(GetDLL.TrayDll)
For Each t As Type In dll.GetTypes
If Not t.GetInterface("IPlugin") = Nothing Then
Try
Dim PluginClass As IPlugin = Type(Activator.CreateInstance(t), IPlugin)
PluginClass.DoSomething()
Catch ex As Exception
MessageBox.Show(ex.ToString())
End Try
End If
Next
End Sub
End Module
Plugin:
Imports PluginContracts
Imports System.Windows.Forms
Public Class Plugin
Implements IPlugin
Public Sub DoSomething() Implements IPlugin.DoSomething
Application.Run(New MyContext)
End Sub
Public Sub New()
End Sub
Public Sub DoExit() Implements IPlugin.DoExit
Application.Exit()
End Sub
Public ReadOnly Property Name As String Implements IPlugin.Name
Get
Name = "First Plugin"
End Get
End Property
End Class
(The plugin app is a Dll with a tray icon in Class “MyContext”)
I have everything working, the plugin loads (with the Tray Icon), but I can't close it and load something else.
I have a FileSystemWatcher that will close the plugin, update the Dll and then reopen it, but it closes the Main App and I can’t do anything else…
Thanks for the help
Sorry for taking so long to answer. This turned out to be a lot more complicated than I thought, at least if you want to impose security restrictions on your plugin.
The unsafe way
If you're okay with your plugin executing in so-called full trust, meaning that it can do anything it wants, then you have to do three things:
Tweak your code to launch the plugin in a separate AppDomain.
Run the DoSomething() method in its own thread (this is necessary since Application.Run() tries to create a new UI thread).
Change IPlugin from an Interface to a MustInherit class. This is because code that is marshalled between different AppDomains MUST run in an object that inherits from MarshalByRefObject.
New code:
EDIT (2018-05-29)
I figured the best way to wait for a plugin to exit in a windowless application would be to utilize a ManualResetEvent.
I have made a quick implementation for this in the code below. You must now call PluginClass.Exit() instead of DoExit() when closing your plugin.
I will add this to the safer solution after some more testing.
Dim PluginDomain As AppDomain = Nothing
Dim PluginClass As PluginBase = Nothing
Sub Main()
LoadTray()
If PluginClass IsNot Nothing Then
PluginClass.WaitForExit()
End If
End Sub
Sub LoadTray()
Dim dll As Assembly = Assembly.LoadFrom(GetDLL.TrayDll)
For Each t As Type In dll.GetTypes
If GetType(PluginBase).IsAssignableFrom(t) = True Then
Try
Dim PluginDomainSetup As New AppDomainSetup() With {
.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
.PrivateBinPath = "Plugins"
}
PluginDomain = AppDomain.CreateDomain("MyPluginDomain", Nothing, PluginDomainSetup)
PluginClass = CType(PluginDomain.CreateInstanceFromAndUnwrap(GetDLL.TrayDll, t.FullName), PluginBase)
Dim PluginThread As New Thread(AddressOf PluginClass.DoSomething)
PluginThread.IsBackground = True
PluginThread.Start()
Catch ex As Exception
MessageBox.Show(ex.ToString())
End Try
Exit For 'Don't forget to exit the loop.
End If
Next
End Sub
PluginBase.vb:
Public MustInherit Class PluginBase
Inherits MarshalByRefObject
Private IsExitingEvent As New ManualResetEvent(False)
Public MustOverride ReadOnly Property Name As String
Public MustOverride Sub DoSomething()
Protected MustOverride Sub OnExit()
Public Sub [Exit]()
Me.OnExit()
Me.IsExitingEvent.Set()
End Sub
Public Sub WaitForExit()
Me.IsExitingEvent.WaitOne(-1)
End Sub
End Class
Your plugin:
Imports PluginContracts
Imports System.Windows.Forms
Public Class Plugin
Inherits PluginBase
Protected Overrides Sub DoSomething()
Application.Run(New MyContext)
End Sub
Protected Overrides Sub OnExit()
Application.Exit()
End Sub
Public Overrides ReadOnly Property Name As String
Get
Return "First Plugin" 'Use "Return" instead of "Name = ..."
End Get
End Property
End Class
However, as letting the plugin run in full trust is extremely unsafe I designed a solution which lets you control what the plugin can do. This required me to rewrite most of the code though, as it needed a different structure to work.
The safe(r) way
In my test case PluginContracts is a separate DLL (project) with only four classes:
PluginBase - The base class for all plugins.
PluginInfo - A wrapper containing info about a plugin (used by PluginManager).
PluginManager - A manager for loading, unloading and keeping track of plugins.
PluginUnloader - A class that runs with full trust in the plugin's restricted AppDomain. Only used to be able to call Application.Exit().
First and foremost, for everything to work the PluginContracts DLL needs to be signed with a Strong Name. Here's how that can be done:
Right-click the PluginContracts project in the Solution Explorer and press Properties.
Select the Signing tab.
Check the check box that says Sign the assembly, leave Delay sign only unchecked.
Open the drop down and press <New...>.
Give the key a file name and password (be sure to remember this!).
Done!
Now that that's fixed you need to make the PluginContracts DLL available to be called by partially trusted code. This is so that our plugin can use it since it will be running as untrusted code.
Select the project in the Solution Explorer again.
Press the button in the Solution Explorer that says Show all files.
Expand the My Project node.
Double-click AssemblyInfo.vb to edit it and add this line at the end of the file:
<Assembly: AllowPartiallyTrustedCallers(PartialTrustVisibilityLevel:=Security.PartialTrustVisibilityLevel.NotVisibleByDefault)>
There is a backside to doing this: All code inside the PluginContracts DLL will now run with rather low permissions. To make it run with the standard permissions again you have to decorate each class with the SecurityCritical attribute, except for the PluginBase class (in the code below I've already fixed all this, so you don't need to change anything). Due to this I recommend that you only have below four classes in the PluginContracts project:
PluginBase.vb:
''' <summary>
''' A base class for application plugins.
''' </summary>
''' <remarks></remarks>
Public MustInherit Class PluginBase
Inherits MarshalByRefObject
Public MustOverride ReadOnly Property Name As String
Public MustOverride Sub DoSomething()
Public MustOverride Sub OnExit()
End Class
PluginInfo.vb:
Imports System.Security
Imports System.Runtime.Remoting
''' <summary>
''' A class holding information about a plugin.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Public Class PluginInfo
Private _file As String
Private _plugin As PluginBase
Private _appDomain As AppDomain
Private Unloader As PluginUnloader
Friend Unloaded As Boolean = False
''' <summary>
''' Gets the AppDomain that this plugin runs in.
''' </summary>
''' <remarks></remarks>
Friend ReadOnly Property AppDomain As AppDomain
Get
Return _appDomain
End Get
End Property
''' <summary>
''' Gets the full path to the plugin assembly.
''' </summary>
''' <remarks></remarks>
Public ReadOnly Property File As String
Get
Return _file
End Get
End Property
''' <summary>
''' Gets the underlying plugin.
''' </summary>
''' <remarks></remarks>
Public ReadOnly Property Plugin As PluginBase
Get
Return _plugin
End Get
End Property
''' <summary>
''' DO NOT USE! See PluginManager.UnloadPlugin() instead.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Friend Sub Unload()
Me.Unloader.Unload()
Me.Unloaded = True
End Sub
''' <summary>
''' Initializes a new instance of the PluginInfo class.
''' </summary>
''' <param name="File">The full path to the plugin assembly.</param>
''' <param name="Plugin">The underlying plugin.</param>
''' <param name="AppDomain">The AppDomain that the plugin runs in.</param>
''' <remarks></remarks>
<SecurityCritical()>
Friend Sub New(ByVal File As String, ByVal Plugin As PluginBase, ByVal AppDomain As AppDomain)
_file = File
_plugin = Plugin
_appDomain = AppDomain
'Create an instance of PluginUnloader inside the plugin's AppDomain.
Dim Handle As ObjectHandle = Activator.CreateInstanceFrom(Me.AppDomain, GetType(PluginUnloader).Module.FullyQualifiedName, GetType(PluginUnloader).FullName)
Me.Unloader = CType(Handle.Unwrap(), PluginUnloader)
End Sub
End Class
PluginManager.vb:
'-------------------------------------------------------------------------------
'Copyright (c) 2018, Vincent Bengtsson
'All rights reserved.
'
'Redistribution and use in source and binary forms, with or without
'modification, are permitted provided that the following conditions are met:
'1. Redistributions of source code must retain the above copyright notice, this
' list of conditions and the following disclaimer.
'2. Redistributions in binary form must reproduce the above copyright notice,
' this list of conditions and the following disclaimer in the documentation
' and/or other materials provided with the distribution.
'
'THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
'ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
'WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
'DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
'ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
'(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
'LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
'ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
'(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
'SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'-------------------------------------------------------------------------------
Imports System.Collections.ObjectModel
Imports System.Reflection
Imports System.IO
Imports System.Security
Imports System.Security.Permissions
Imports System.Security.Policy
''' <summary>
''' A class for managing application plugins.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Public NotInheritable Class PluginManager
Implements IDisposable
Private PluginLookup As New Dictionary(Of String, PluginInfo)
Private PluginList As New List(Of String)
Private CurrentAppDomain As AppDomain = Nothing
Private _loadedPlugins As New ReadOnlyCollection(Of String)(Me.PluginList)
''' <summary>
''' Gets a list of all loaded plugins' names.
''' </summary>
''' <remarks></remarks>
Public ReadOnly Property LoadedPlugins As ReadOnlyCollection(Of String)
Get
Return _loadedPlugins
End Get
End Property
''' <summary>
''' Returns the plugin with the specified name (or null, if the plugin isn't loaded).
''' </summary>
''' <param name="Name">The name of the plugin to get.</param>
''' <remarks></remarks>
<SecurityCritical()>
Public Function GetPluginByName(ByVal Name As String) As PluginInfo
Dim Plugin As PluginInfo = Nothing
Me.PluginLookup.TryGetValue(Name, Plugin)
Return Plugin
End Function
''' <summary>
''' Checks whether a plugin by the specified name is loaded.
''' </summary>
''' <param name="Name">The name of the plugin to look for.</param>
''' <remarks></remarks>
<SecurityCritical()>
Public Function IsPluginLoaded(ByVal Name As String) As Boolean
Return Me.PluginLookup.ContainsKey(Name)
End Function
''' <summary>
''' Loads a plugin with the specified permissions (or no permissions, if omitted).
''' </summary>
''' <param name="File">The path to the plugin assembly to load.</param>
''' <param name="Permissions">Optional. A list of permissions to give the plugin (default permissions that are always applied: SecurityPermissionFlag.Execution).</param>
''' <remarks></remarks>
<SecurityCritical()>
Public Function LoadPlugin(ByVal File As String, ByVal ParamArray Permissions As IPermission()) As PluginInfo
Dim FullPath As String = Path.GetFullPath(File)
If System.IO.File.Exists(FullPath) = False Then Throw New FileNotFoundException()
'Check if the plugin file has already been loaded. This is to avoid odd errors caused by Assembly.LoadFrom().
If Me.PluginLookup.Values.Any(Function(info As PluginInfo) info.File.Equals(FullPath, StringComparison.OrdinalIgnoreCase)) = True Then
Throw New ApplicationException("Plugin """ & FullPath & """ is already loaded!")
End If
'Load assembly and look for a type derived from PluginBase.
Dim PluginAssembly As Assembly = Assembly.LoadFrom(FullPath)
Dim PluginType As Type = PluginManager.GetPluginType(PluginAssembly)
If PluginType Is Nothing Then Throw New TypeLoadException("""" & FullPath & """ is not a valid plugin!")
'Set up the application domain.
'Setting PartialTrustVisibleAssemblies allows our plugin to make partially trusted calls to the PluginBase DLL.
Dim PluginDomainSetup As New AppDomainSetup() With {
.ApplicationBase = Me.CurrentAppDomain.BaseDirectory,
.PartialTrustVisibleAssemblies = New String() {GetType(PluginUnloader).Assembly.GetName().Name & ", PublicKey=" & BitConverter.ToString(GetType(PluginUnloader).Assembly.GetName().GetPublicKey()).ToLower().Replace("-", "")}
}
'Set up the default (necessary) permissions for the plugin:
' SecurityPermissionFlag.Execution - Allows our plugin to execute managed code.
' FileIOPermissionAccess.Read - Allows our plugin to read its own assembly.
' FileIOPermissionAccess.PathDiscovery - Allows our plugin to get information about its parent directory.
Dim PluginPermissions As New PermissionSet(PermissionState.None) 'No permissions to begin with.
PluginPermissions.AddPermission(New SecurityPermission(SecurityPermissionFlag.Execution))
PluginPermissions.AddPermission(New FileIOPermission(FileIOPermissionAccess.Read Or FileIOPermissionAccess.PathDiscovery, FullPath))
'Load all additional permissions (if any).
For Each Permission As IPermission In Permissions
PluginPermissions.AddPermission(Permission)
Next
'Get the strong name for the assembly containing PluginUnloader and create the AppDomain.
'The strong name is used so that PluginUnloader may bypass the above added restrictions.
Dim TrustedAssembly As StrongName = GetType(PluginUnloader).Assembly.Evidence.GetHostEvidence(Of StrongName)()
Dim PluginDomain As AppDomain = AppDomain.CreateDomain(File, Nothing, PluginDomainSetup, PluginPermissions, TrustedAssembly)
'Create an instance of the plugin.
Dim Plugin As PluginBase = CType(PluginDomain.CreateInstanceFromAndUnwrap(FullPath, PluginType.FullName), PluginBase)
Dim PluginInfo As New PluginInfo(FullPath, Plugin, PluginDomain)
'Is a plugin by this name already loaded?
If Me.IsPluginLoaded(Plugin.Name) = True Then
Dim Name As String = Plugin.Name
Me.UnloadPlugin(PluginInfo)
Throw New ApplicationException("A plugin by the name """ & Name & """ is already loaded!")
End If
'Add the plugin to our lookup table and name list.
Me.PluginLookup.Add(Plugin.Name, PluginInfo)
Me.PluginList.Add(Plugin.Name)
'Return the loaded plugin to the caller.
Return PluginInfo
End Function
''' <summary>
''' Unloads a plugin.
''' </summary>
''' <param name="Name">The name of the plugin to unload.</param>
''' <remarks></remarks>
<SecurityCritical()>
Public Sub UnloadPlugin(ByVal Name As String)
Dim Plugin As PluginInfo = Me.GetPluginByName(Name)
If Plugin Is Nothing Then Throw New ArgumentException("No plugin by the name """ & Name & """ is loaded.", "Name")
Me.UnloadPlugin(Plugin)
End Sub
''' <summary>
''' Unloads a plugin.
''' </summary>
''' <param name="PluginInfo">The plugin to unload.</param>
''' <remarks></remarks>
<SecurityCritical()>
Public Sub UnloadPlugin(ByVal PluginInfo As PluginInfo)
If PluginInfo Is Nothing Then Throw New ArgumentNullException("PluginInfo")
If PluginInfo.Unloaded = True Then Return
Dim PluginName As String = PluginInfo.Plugin.Name
Dim Permission As New SecurityPermission(SecurityPermissionFlag.ControlAppDomain)
Permission.Assert()
PluginInfo.Plugin.OnExit()
PluginInfo.Unload()
AppDomain.Unload(PluginInfo.AppDomain)
CodeAccessPermission.RevertAssert()
Me.PluginLookup.Remove(PluginName)
End Sub
''' <summary>
''' Attempts to get a class derived from PluginBase in the specified assembly.
''' </summary>
''' <param name="PluginAssembly">The assembly to check.</param>
''' <remarks></remarks>
<SecurityCritical()>
Private Shared Function GetPluginType(ByVal PluginAssembly As Assembly) As Type
For Each t As Type In PluginAssembly.GetTypes()
If GetType(PluginBase).IsAssignableFrom(t) = True Then Return t
Next
Return Nothing
End Function
''' <summary>
''' Initializes a new instance of the PluginManager class.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Public Sub New()
Me.CurrentAppDomain = AppDomain.CurrentDomain
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
<SecurityCritical()>
Protected Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
'Unload all plugins.
For Each PluginPair As KeyValuePair(Of String, PluginInfo) In Me.PluginLookup
Try : Me.UnloadPlugin(PluginPair.Value) : Catch : End Try
Next
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
<SecurityCritical()>
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
PluginUnloader.vb:
Imports System.Windows.Forms
Imports System.Security
Imports System.Security.Permissions
''' <summary>
''' A class for unloading plugins from within their AppDomains.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Public NotInheritable Class PluginUnloader
Inherits MarshalByRefObject
''' <summary>
''' Calls Application.Exit(). This must be called inside the plugin's AppDomain.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Public Sub Unload()
'Request permission to execute managed code (required to call Application.Exit()).
Dim Permission As New SecurityPermission(SecurityPermissionFlag.UnmanagedCode)
Permission.Assert()
'Exits the plugin's UI threads (if any exist).
Application.Exit()
'Revert UnmanagedCode privilege.
CodeAccessPermission.RevertAssert()
End Sub
''' <summary>
''' Initializes a new instance of the PluginUnloader class.
''' </summary>
''' <remarks></remarks>
<SecurityCritical()>
Public Sub New()
End Sub
End Class
Example usage
Main code (currently used in a form):
Dim PluginManager As New PluginManager
Dim PluginClass As PluginInfo
Private Sub RunPluginButton_Click(sender As System.Object, e As System.EventArgs) Handles RunPluginButton.Click
'Load our plugin and give it UI permissions.
'The "UIPermissionWindow.AllWindows" flag allows our plugin to create a user interface (display windows, notify icons, etc.).
PluginClass = PluginManager.LoadPlugin("Plugins\TestPlugin.dll", New UIPermission(UIPermissionWindow.AllWindows))
'IMPORTANT: Each plugin must run in its own thread!
Dim tr As New Thread(AddressOf PluginClass.Plugin.DoSomething)
tr.IsBackground = True
tr.Start()
End Sub
Private Sub ExitPluginButton_Click(sender As System.Object, e As System.EventArgs) Handles ExitPluginButton.Click
If PluginClass Is Nothing Then
MessageBox.Show("Plugin not loaded!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Return
End If
PluginManager.UnloadPlugin(PluginClass)
PluginClass = Nothing
End Sub
Example plugin:
Public Class TestPlugin
Inherits PluginBase
Public Overrides ReadOnly Property Name As String
Get
Return "Test Plugin"
End Get
End Property
Public Overrides Sub DoSomething()
Application.Run(New MyContext)
End Sub
Public Overrides Sub OnExit()
'Do some cleanup here, if necessary.
'No need to call Application.Exit() - it is called automatically by PluginUnloader.
End Sub
End Class
How permissions work
When you call the PluginManager.LoadPlugin() method you pass it a path to the plugin which to load, however you can also pass it a set of permissions that you want to apply to the plugin (if you want to, this is optional).
By default all plugins are loaded only with these permissions:
It can execute its own managed code.
It has read access to the plugin file itself and its directory.
This means that a plugin:
Can not execute any unmanaged (also known as native) code. This is for example DllImport or Declare Function declarations.
Can not read/write/create/delete any files.
Can not have a User Interface (open any windows, use notify icons, etc.).
Does not have internet access.
Can not run any code other than its own and the framework's (within the boundaries of its restrictions).
...and so on, and so forth...
This can be changed by specifying which permissions the plugin should be granted, when you're loading it. For instance if you want your plugin to be able to read and write files in a certain directory you could do:
PluginManager.LoadPlugin("Plugins\TestPlugin.dll",
New FileIOPermission(FileIOPermissionAccess.AllAccess, "C:\some\folder"))
Or if you want it to be able to access any folder or file:
PluginManager.LoadPlugin("Plugins\TestPlugin.dll",
New FileIOPermission(PermissionState.Unrestricted))
Multiple permissions can be added by just keep adding arguments:
PluginManager.LoadPlugin("Plugins\TestPlugin.dll",
New FileIOPermission(PermissionState.Unrestricted),
New UIPermission(UIPermissionWindow.AllWindows),
New WebPermission(PermissionState.Unrestricted))
For a list of all available permission types see:
https://msdn.microsoft.com/en-us/library/h846e9b3(v=vs.110).aspx
I have a service in VB.NET (2010) that is receiving a UDP packets with UDPClient.BeginReceive asynchronously.
Everything is working fine, I'm able to receive a packet data - but I'm not able to get sender (or remote) endpoint.
There is a CallBack routine that is receiving an IAsyncResult object, that is represented by System.net.sockets.OverlappedAsyncResult where in property SocketAddress is a byte array where is a sender port and sender IP address.
But it is accessible only in debug time - I'm using a breakpoint in async callback routine.
Im unable to cast IAsyncResult as OverlappedAsyncResult and I'm not able to access the socket address in design time. It is giving me an error that means that object is not found and it can't be declared.
I'm attaching a class that I use for it for reference. Is there any better way to do it, or can I get the sender IP?
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Public Class UDPInterface
Implements IDisposable
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
' TODO: dispose managed state (managed objects).
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
Private _EP As UdpClient
Private _RemIPEP As System.Net.IPEndPoint
Private _Port As Integer
Public Sub New(Port As Integer)
_Port = Port
_RemIPEP = New IPEndPoint(System.Net.IPAddress.Any, Port)
_EP = New UdpClient
_EP.Client.Bind(_RemIPEP)
_EP.BeginReceive(AddressOf Receive, _EP)
End Sub
Public Sub SendPacket(Bytes As Byte())
_EP.Send(Bytes, Bytes.Length, New IPEndPoint(System.Net.IPAddress.Broadcast, _Port))
End Sub
Private Sub Receive(rec As IAsyncResult)
Dim RX As Byte() = _EP.EndReceive(rec, _RemIPEP)
Debug.Print(Encoding.ASCII.GetString(RX))
_EP.BeginReceive(AddressOf Receive, _EP)
RaiseEvent PacketReceived(RX)
End Sub
Public Event PacketReceived(Bytes As Byte())
End Class
I have a test database and a live database located on different servers. My current design manages to work with both but I'd like to improve the access. I am out of ideas by now and want your opinion.
Question:
How can I solve the following design problem?
I created two EF6 classes to access the different databases. (Highlighted ones)
On program start I define a default connection depending on the build mode.
#If DEBUG Then
myDbType = AbstractDBAccess.DatabaseType.Test
#Else
myDbType = AbstractDBAccess.DatabaseType.Live
#End If
I then use myDBType to create DBAccess objects to interact with my DB. From now on it automatically takes care to connect to either test or live DB.
Dim userAccess = new UserDBAccess(myDBtype)
userAccess.GetUser()
userAccess.Dispose()
Dim projectAccess = new ProjectDBAccess(myDBType)
projectAccess.DoWork()
projectAccess.Dispose()
I got this idea after watching a SW-Architecture video on youtube https://www.youtube.com/watch?v=sA-Hp4aBWb4 which I modified to my needs.
So until now this looks like a very clean way but I run into trouble.
My problem is that for each database access I have to copy/paste 99% of my code depending on which server I want to access. E.G.
For the live DB: ctxLive < #see code below
For the testDB: ctxTest < #see code below
I have a base class from which all DBAccess classes derive.
Imports System.Data.Entity
Public MustInherit Class AbstractDBAccess
Implements IDisposable
#Region "Fields"
' Access live db via EF 6
Protected ctxLive As DBLiveEntities
' Access test db via EF 6
Protected ctxTest As DBTestEntities
' Remember DB to access
Protected myDBType As DatabaseType
#End Region
#Region "Enum"
''' <summary>
''' Add more data bases here.
''' </summary>
''' <remarks>Matthias Köhler</remarks>
Public Enum DatabaseType
Live = 0
Test = 1
End Enum
#End Region
#Region "Constructor"
Public Sub New(ByVal dbType As AbstractDBAccess.DatabaseType)
myDBType = dbType '
' Depending on what type we get from startup we grant access to test or live DB
Select Case dbType
Case DatabaseType.Live
Me.ctxLive = New DBLiveEntities
Case DatabaseType.Test
Me.ctxTest = New DBTestEntities
End Select
End Sub
#End Region
#Region "Methods"
Public Function GetDBAccess() As DbContext
' My Problem is i need to return two different types in this method.
' After creating an instance I save which access this object was intended for with "myDBType"
' Both classes derive from DbContext but if I implement it this way I can't see my tables. See screenshot below.
Select Case myDBType
Case DatabaseType.Live
Return Me.ctxLive
Case DatabaseType.Test
Return Me.ctxTest
End Select
Throw New Exception("No matching case for " & myDBType.ToString)
End Function
#End Region
The Problem:
You see the Select-Case is 99% the same. Imagine this for complicated code and 15 classes. I just don't like that copy pasting. I just need to change the "ctxLive" or "ctxTest".
Imagine that someone has to add another DB in some years. He has to go through the whole code and add a case to each method.
Isn't there a better way?
Here the matching code for this screenshot.
Public Class UserDBAccess
Inherits AbstractDBAccess
Implements IDisposable
Public Sub New(ByVal dbType As AbstractDBAccess.DatabaseType)
MyBase.New(dbType)
End Sub
Public Sub GetUser()
' Currently I have to add a lot of select cases to seperate my live DB and test DB.
' They have different connection strings and are on different servers
Select Case Me.myDBType
Case DatabaseType.Live
Me.ctxLive.CCTUsers.Where(Function(u) u.UserName.Contains("StackOverflow"))
Case DatabaseType.Test
Me.ctxTest.CCTUsers.Where(Function(u) u.UserName.Contains("StackOverflow"))
End Select
' I have a lot of Copy Pasting which in my opinion is ugly.
' I want sth like this to save me all that select cases
' The difference here is the "GetDBAccess"
Me.GetDBAccess.CCTUsers.Where(Function(u) u.UserName.Contains("StackOverflow"))
End Sub
End Class
The following solution is definitely cleaner and easier to maintain in future.
Add connection strings to your app.config
<connectionStrings>
<add name="DB_Live" connectionString="liveDB" providerName="System.Data.EntityClient" />
<add name="DB_Test" connectionString="testDB" providerName="System.Data.EntityClient" />
</connectionStrings>
Create a DBEntityManager
Public Class DBEntityManager
Inherits DbContext
Public Sub New(ByVal connString As String)
MyBase.New(connString)
End Sub
Public Overridable Property MyTable() As DbSet(Of MyTable)
End Class
Then I added a reference to the ConfigurationManager:
Right click your project (Not the solution)
Add -> Reference ... -> Framework
Search for "Configuration" and activate the check box for "System.Configuration"
Click "OK"
On start up I configure the connection string depending on the build mode
#If DEBUG Then
connString = Configuration.ConfigurationManager.ConnectionStrings("DB_Test").ConnectionString()
#Else
connString = Configuration.ConfigurationManager.ConnectionStrings("DB_Live").ConnectionString()
#End If
I re-factored my base class to look like this.
It now takes the connection string and creates a DBEntityManager object.
DBEntityManager derives from DBContext and works with the connection string passed.
Public Class DBAccessAbstract
Implements IDisposable
#Region "Field Declaration"
Protected ctx As DBEntityManager
#End Region
#Region "Constructors"
Public Sub New(ByVal connString As String)
ctx = New DBEntityManager(connString)
End Sub
#End Region
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
Me.ctx.Dispose()
End If
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources.
'Protected Overrides Sub Finalize()
' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.
' Dispose(False)
' MyBase.Finalize()
'End Sub
' This code added by Visual Basic to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
For each table I create accessors as I wanted to
Public Class DBAccessUserTable
Inherits DBAccessAbstract
Implements IDisposeable
Public Sub New(ByVal connString as String)
MyBase.New(connString)
End Sub
Public Function Exists(ByVal userName As String) As Boolean
Dim user As UserTable
user = Me.ctx.UserTables.Where(Function(e) e.UserName.Contains("StackOverflow")).FirstOrDefault
If IsNothing(user) Then Return False
Return True
End Function
End Class
Now I can access either test or live DB automatically by creating a new instance of my DBAccess class
Dim dbEmpl As New DBAccessUserTable(Me.connString)
If Not dbEmpl.Exists(userName) Then Throw New System.Exception(userName & " doesn't exist.")
MessageBox.Show("True!")
dbEmpl.Dispose()
Thanks for giving me the idea!
--Original Post--
I am trying to manage (start/stop) a windows service on a remote machine using alternate credentials. I know that I can use the ServiceController class to manage a service using my current credentials:
Dim sc As New ServiceController(ServiceName, ComputerName)
but I want to use different credentials. The other classes I am using (DirectoryEntry and System.Management) both support using alternate credentials... Help would be greatly appreciated.
--Working Code (built based off accepted answer)--
I have to admit that I was sceptical it would work... but below is the code. I had to make a minor change to the code you suggested. Whenever I tried IPC$ it would return a 53 result code, even though I'm sure the share exists. So at the suggestion of another website I removed the share and just the computer name and this worked.
Imports System.Runtime.InteropServices
Imports System.Net
Imports System.IO
Imports System.ServiceProcess
Module Module1
Sub Main()
Dim Computername As String = "SomeComputer"
'Create connection to remote computer'
Using nc As New NetworkConnection("\\" + Computername, New NetworkCredential("Domain\User", "Password"))
Dim sc As New ServiceController("Windows Firewall/Internet Connection Sharing (ICS)", Computername)
'now we can start/stop/whatever we want here'
End Using
Console.ReadLine()
End Sub
Public Class NetworkConnection
Implements IDisposable
Private _networkName As String
Public Sub New(ByVal networkName As String, ByVal credentials As NetworkCredential)
_networkName = networkName
Dim netResource = New NetResource() With { _
.Scope = ResourceScope.GlobalNetwork, _
.ResourceType = ResourceType.Disk, _
.DisplayType = ResourceDisplaytype.Share, _
.RemoteName = networkName _
}
Dim result = WNetAddConnection2(netResource, credentials.Password, credentials.UserName, 0)
If result <> 0 Then
Throw New IOException("Error connecting to remote share", result)
End If
End Sub
Protected Overrides Sub Finalize()
Try
Dispose(False)
Finally
MyBase.Finalize()
End Try
End Sub
Public Sub Dispose() Implements System.IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Sub Dispose(ByVal disposing As Boolean)
WNetCancelConnection2(_networkName, 0, True)
End Sub
<DllImport("mpr.dll")> _
Private Shared Function WNetAddConnection2(ByVal netResource As NetResource, ByVal password As String, ByVal username As String, ByVal flags As Integer) As Integer
End Function
<DllImport("mpr.dll")> _
Private Shared Function WNetCancelConnection2(ByVal name As String, ByVal flags As Integer, ByVal force As Boolean) As Integer
End Function
End Class
<StructLayout(LayoutKind.Sequential)> _
Public Class NetResource
Public Scope As ResourceScope
Public ResourceType As ResourceType
Public DisplayType As ResourceDisplaytype
Public Usage As Integer
Public LocalName As String
Public RemoteName As String
Public Comment As String
Public Provider As String
End Class
Public Enum ResourceScope As Integer
Connected = 1
GlobalNetwork
Remembered
Recent
Context
End Enum
Public Enum ResourceType As Integer
Any = 0
Disk = 1
Print = 2
Reserved = 8
End Enum
Public Enum ResourceDisplaytype As Integer
Generic = &H0
Domain = &H1
Server = &H2
Share = &H3
File = &H4
Group = &H5
Network = &H6
Root = &H7
Shareadmin = &H8
Directory = &H9
Tree = &HA
Ndscontainer = &HB
End Enum
End Module
To make remote login you should use WNetAddConnection2 (see http://msdn.microsoft.com/en-us/library/aa385413.aspx) or NetUseAdd (see http://msdn.microsoft.com/en-us/library/aa370645.aspx) API. You can use \\RemoteComputer\IPC$ as the destination resource.
UPDATED based on the question from the comment: The explanation about IPC$ sessions can be long. Just the main information.
If you want to do something on a remote computer the first thing which will be done is the establishing a authenticated "connection" to the remote computer. The network login (remote login) on the remote computer will be done, which works quite other as a local login. The network logon session stay holding and if you have a connection to for example \\RemoteComputer\share1 and one other program on your computer try access for example \\RemoteComputer\share2, the same session will be used.
You can simulate the situation with net.exe. Just start cmd.exe and type
net use \\RemoteComputer\IPC$ /u:Domain\User password
or
net use \\RemoteComputer\IPC$ /u:RemoteComputer\LocalRemoteUser password
then you will have a connection to the destination computer. Then you can type \\RemoteComputer\AnyShare in Explorer and access file system under the user's Domain\User or RemoteComputer\LocalRemoteUser credential. To disconnect use
net use \\RemoteComputer\IPC /d
If you try to start/stop a service on the remote computer the same IPC session will be tried to established. If you have already such session with one of user's credentials it will be used. Functions WNetAddConnection2, NetUseAdd can be used as replacement of "net use". If you permanently want to access a remote computer with other user's credentials you can use CredWrite, CredWriteDomainCredentials or CredUIPromptForCredentials / CredUIPromptForWindowsCredentials. The Cred-function seems me not the best way for your case.