I am trying to create an msi install for my windows service. The reason for creating an msi is that the intended users want to be able to quickly install the service with as-little intervention as possible.
i can get the service to install as a msi but i have a variable within my code that i need the user to define when the msi is being installed. the variable i require from the user is the file path which they want the xml files my service creates to be located.
i thought i could configure the app.config application settings to contain the file path that the xml files should be written to. However i'm struggling to do this and im not to sure if its the best way to do it?
I have my setup project that contains my executable and has my textbox which will contain the one variable from the user.
I have an installer class which contains my serviceinstaller and process installer. This is where im struggling to understand what i need to do next.
Do i need to override the install method? The current code of my installer class was automatically generated and is as follows:
Imports System.Configuration.Install
Imports System.Configuration
<System.ComponentModel.RunInstaller(True)> Partial Class ProjectInstaller
Inherits System.Configuration.Install.Installer
'Installer overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Component Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Component Designer
'It can be modified using the Component Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.ServiceProcessInstaller1 = New System.ServiceProcess.ServiceProcessInstaller()
Me.ServiceInstaller1 = New System.ServiceProcess.ServiceInstaller()
'
'ServiceProcessInstaller1
'
Me.ServiceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem
Me.ServiceProcessInstaller1.Password = Nothing
Me.ServiceProcessInstaller1.Username = Nothing
'
'ServiceInstaller1
'
Me.ServiceInstaller1.ServiceName = "Spotter"
Me.ServiceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic
'
'ProjectInstaller
'
Me.Installers.AddRange(New System.Configuration.Install.Installer() {Me.ServiceProcessInstaller1, Me.ServiceInstaller1})
End Sub
Friend WithEvents ServiceProcessInstaller1 As System.ServiceProcess.ServiceProcessInstaller
Friend WithEvents ServiceInstaller1 As System.ServiceProcess.ServiceInstaller
End Class
I can even add the CustomActionData values. The string determines what gets passed into the context object that i used to collect the user value that is entered. param1 being my variable name.
I'm pretty much struggling with the installer code...i think?
One option to consider is using a third party installer, such as Installshield, that has builtin support for modification of xml configuration files, such as config files.
However, if you want to roll your own, you definitely need to override the Install method. Any parameters that you pass in CustomData will be available in the dictionary that is passed as a parameter to this method.
For example:
Public Overrides Sub Install(ByVal stateSaver As System.Collections.IDictionary)
MyBase.Install(stateSaver)
If Me.Context.Parameters.Count <> 0 Then
For Each sKey As String In Context.Parameters.Keys
Select Case sKey.ToUpper
Case "PARAM1"
' XML directory
Me.XMLDir = Context.Parameters(sKey)
End Select
Next
End If
End Sub
In cases like this, we always write the value to the registry so that the user doesn't have to re-enter it if they uninstall or reinstall.
I am not sure of the exact sequence of events for modifying the app.config, but you could write to the registry, then modify app.config when the service is first started.
You will probably also find that you need to remove the custom action from the Commit phase in order for your installer to work successfully.
Related
I know this appears to be a rather common topic and should have been resolved from earlier posts. But what I am experiencing still does not seem to have a solution online:
I have a form called ExpenseEntry in which there is a sub procedure called Public Sub OpenVoucher.
I want to call this sub from another form for which I use the following code:
Dim ExpForm As New ExpenseEntry
ExpForm.Show()
ExpForm.OpenVoucher()
While this works well enough, the problem is everytime I click the button, a new window of ExpenseEntry is launched. As per how I have designed the application, repeat windows is not permissible and only one window should be available at a time.
I have tried various methods to restrict more than one form such as by using a variable to control the form but that gives rise to other issues.
If I use Application.OpenForms but still does not resolve the issue.
I have earlier queried in this regard in the following link:
Textbox not refreshing
I am using VB.NET 2019 which does not allow the launch of default instance of a form like Form.Show. I know this is bad practice but it was easier to manage with that till VB.NET 2017.
Now by creating a form variable and launching that creates an infinite loop where I cannot have just one instance of a form running on a single thread.
The really simple way to handle this is to use the default instance of the form type. In VB, since 2005, each form type has a default instance that you can access via the type name. Read here for more info. In your case, you can do this:
'Display the form if it is not already displayed.
ExpenseEntry.Show()
'Activate the form if it is already displayed.
ExpenseEntry.Activate()
'Do the deed.
ExpenseEntry.OpenVouncher()
That said, default instances are a bit dodgy. They do enable beginners to access forms from anywhere in their project under certain circumstances but they also have limitations that can cause issues. Most importantly though, they help to prevent you learning proper OOP by treating forms differently to other types. If you want to do this the way a proper developer would then simply declare a variable to refer to the current instance of the form:
Private expenseEntryDialogue As ExpenseEntry
When it's time to use the form, you simply check whether that variable refers to a usable instance and use it if it does, otherwise create a new one:
If expenseEntryDialogue Is Nothing OrElse expenseEntryDialogue.IsDisposed Then
expenseEntryDialogue = New ExpenseEntry
End If
expenseEntryDialogue.Show()
expenseEntryDialogue.Activate()
expenseEntryDialogue.OpenVoucher()
A third option would be to implement your own singleton, i.e. a type that can only ever have a single instance. You probably wouldn't do that in VB, given that the default instance is basically a thread-specific singleton and does more automatically but, if you wanted to, you could do this:
Public Class ExpenseEntry
Private Shared _instance As ExpenseEntry
'The one and only instance of the type.
Public Shared ReadOnly Property Instance As ExpenseEntry
Get
If _instance Is Nothing OrElse _instance.IsDisposed Then
_instance = New ExpenseEntry
End If
Return _instance
End Get
End Property
'The constructor is private to prevent external instantiation.
Private Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
End Class
and then this:
ExpenseEntry.Instance.Show()
ExpenseEntry.Instance.Activate()
ExpenseEntry.Instance.OpenVoucher()
1>------ Build started: Project: Major 2, Configuration: Debug Any CPU ------
1> COM Reference 'WMPLib' is the interop assembly for ActiveX control 'AxWMPLib' but was marked to be linked by the compiler with the /link flag. This COM reference will be treated as a reference and will not be linked.
1>C:\Users\James\Google Drive\School\SDD\VB Work\VB Task 2\Major 2\Major 2\My Project\Application1.Designer.vb(25,20): error BC30269: 'Public Sub New()' has multiple definitions with identical signatures.
1>C:\Users\James\Google Drive\School\SDD\VB Work\VB Task 2\Major 2\Major 2\My Project\Application1.Designer.vb(34,33): error BC30269: 'Protected Overrides Sub OnCreateMainForm()' has multiple definitions with identical signatures.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
What Have I done wrong?
Namespace My
'NOTE: This file is auto-generated; do not modify it directly. To make changes,
' or if you encounter build errors in this file, go to the Project Designer
' (go to Project Properties or double-click the My Project node in
' Solution Explorer), and make changes on the Application tab.
'
Partial Friend Class MyApplication
<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
Public Sub New()
MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.AuthenticationMode.Windows)
Me.IsSingleInstance = false
Me.EnableVisualStyles = true
Me.SaveMySettingsOnExit = true
Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterAllFormsClose
End Sub
<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
Protected Overrides Sub OnCreateMainForm()
Me.MainForm = Global.Major_2.Main_Screen
End Sub
End Class
End Namespace
This is the double up in the code but i don't know what too change in the Application Designer.
Cause of error:
It seems that VS sometimes duplicating the Application.Designer.vb files under the Application.myapp section, (or it could be caused by the human error like the author did.) and for the correctly working project it should only have ONE Application.Designer.vb file.
Solution:
To fix this error, you need to delete the extra Application.Designer.vb files. The result should be looks like the picture given in below.
So turns out google drive duplicated one of my files so there was two and it was opening both
I have a small test project for checking application version from a server and prompting a user to update. Everything works fine except that I can't save a type of System.Version to My.Settings. (I want to save the new version in case a user requests not to be reminded about it again.)
Now, I know I can save Version as a string and convert it back and forth - which I have done to get around this problem - but as System.Version is listed in the available setting data types, I figure it should work. But instead of saving the version, it merely saves an XML entry "Version" without a value (refer below).
I'm using VB.NET, VS 2013, .NET 4.
Here's some code to look at:
Settings.Designer.vb
<Global.System.Configuration.UserScopedSettingAttribute(), _
Global.System.Diagnostics.DebuggerNonUserCodeAttribute()> _
Public Property DoNotRemindVersion() As Global.System.Version
Get
Return CType(Me("DoNotRemindVersion"),Global.System.Version)
End Get
Set
Me("DoNotRemindVersion") = value
End Set
End Property
Example assignment
If My.Settings.DoNotRemind Then My.Settings.DoNotRemindVersion = oVersion.NewVersion
(oVersion.NewVersion is of type System.Version.)
As saved in user.config
<setting name="DoNotRemindVersion" serializeAs="Xml">
<value>
<Version xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</value>
</setting>
So, what am I doing wrong? I've read a few posts about having to serialize classes to save them to user settings, but this is a simple Version number, which, on the face of things, I would expect to be a supported data type.
The System.Version Class is marked with the SerializableAttribute but you will need to mark the property with the SettingsSerializeAsAttribute and pass the System.Configuration.SettingsSerializeAs.Binary value.
You can not use the Project Settings designer interface to automatically generate the code like you have found in the Settings.designer.vb file. You will need to extend the My Namespace - MySettings Class yourself. This is one of areas that partial classes are useful.
Add a new class file to your project and name something creative like CustomMySettings. Select and delete the auto-generated code in this new file and replace it with the following code.
Namespace My
Partial Friend NotInheritable Class MySettings
' The trick here is to tell it serialize as binary
<Global.System.Configuration.SettingsSerializeAs(System.Configuration.SettingsSerializeAs.Binary)> _
<Global.System.Configuration.UserScopedSettingAttribute(), _
Global.System.Diagnostics.DebuggerNonUserCodeAttribute()> _
Public Property DoNotRemindVersion() As Global.System.Version
Get
Return CType(Me("DoNotRemindVersion"), Global.System.Version)
End Get
Set(value As Global.System.Version)
Me("DoNotRemindVersion") = value
End Set
End Property
End Class
End Namespace
This will allow you to use My.Settings.DoNotRemindVersion just like if you created the setting via the designer. The first time you access the setting, it will have a null (Nothing) value, so you may what to initialize it with something like the following in a Form.Load event handler.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim savedVersion As Version = My.Settings.DoNotRemindVersion
If savedVersion Is Nothing Then
My.Settings.DoNotRemindVersion = New Version(1, 0)
My.Settings.Save()
End If
End Sub
I have multiple project solution and need some service keystrokes to be common for all projects of that solution.
For example if I press Ctrl+Alt+Right Shift anywhere and any time in any of included project that "something" happens actually that I can detect that keystroke immediately.
For all those projects I have common code in additional project which is included in all other projects as reference to "common.dll" so that may be right place to put this code.
Any idea on how to make that task and how that code should look like?
I am thinking on detecting those keystrokes at PreFilter Message but I can't make that without help.
Maybe is different kind of solution better that one I think?
Windows Forms raises keyboard events for you so you do not need to go as low level as handling window messages yourself. To standardise the approach across multiple forms and projects, create a common DLL project and add a reference to it in all of the other projects. Create a static class (module in VB) in the common project along the lines of the code below.
Imports System.Windows.Forms
Public Module KeyPressHandler
''' <summary>
''' Connects the <see cref="Form.KeyDown"/> event handler for a specified Windows form
''' </summary>
Public Sub ConnectKeyHandler(form As Form)
AddHandler form.KeyDown, New KeyEventHandler(AddressOf KeyPressHandler.KeyDownHandler)
End Sub
''' <summary>
''' Handles the KeyDown event for a windows form
''' </summary>
Private Sub KeyDownHandler(sender As Object, e As KeyEventArgs)
If (e.KeyData.HasFlag(Keys.Control) AndAlso e.KeyData.HasFlag(Keys.Alt) AndAlso e.KeyData.HasFlag(Keys.Shift)) Then
'' Do whatever you want here
End If
End Sub
End Module
Then in the code-behind of each form, in the constructor, add the following line of code:
Common.KeyPressHandler.ConnectKeyHandler(Me)
(so it will end up looking like this):
Public Class Form1
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Common.KeyPressHandler.ConnectKeyHandler(Me)
End Sub
End Class
The HasFlag method on enums was added in .NET 4, so if you are using a version before that then the logic will be a little more long winded:
If ((e.KeyData And Keys.Control = Keys.Control) AndAlso (e.KeyData And Keys.Alt = Keys.Alt) AndAlso (e.KeyData And Keys.Shift = Keys.Shift)) Then
There is still a problem with specifically detecting the right shift key as opposed to any shift key. I couldn't find a working way to differentiate it from the left shift key, although it looked like using RShift was supposed to do it. One solution might be to call the Windows API function GetKeyState (see http://www.pinvoke.net/default.aspx/user32.getkeystate) with the VK_RSHIFT constant. The historical reason for this is probably to do with the Left and Right shift key distinction being a later addition to Windows than undifferentiated shift keys.
You should create one 'controller' project, which has a keyboard hook, and , maybe a fancy tray icon with animation :)
And from that 'central' project, pass the commands to the other forms...
After looking through many sites and some tutorial videos, I'm still stumped on this one. I'm finishing up a program and I need to add one last bit of functionality.
The program works this way. The user specifies a file in textbox1 and then specifies a directory in textbox2. The user sets how often they want the file to by copied in textbox3. The user hits run and the program copies the file to the new location, adding a number to the file name each time it is copied (to avoid overwrites). That all works fine, but I want the user to have the choice to either copy the file by time or when the file is modified.
How can I use the FileSystemWatcher to look for modification in the directory (given in textbox1) and then call the statement that copies the specified directory to the target destination (specified in textbox 2)?
Additional Note:
In one tutorial the FileSystemWatcher path was set up by doing this
Dim watched As String = System.IO.Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "Pictures")
Dim fsw As New FileSystemWatcher(watched)
The path that the code directs to is "C:\Users[User Name]\Pictures" .
I can't find a resource online that shows what variables ".GetEnvironmentVariable" accepts or even what the variables mean. This is one of many reasons why I am having trouble with this last bit code.
GetEnvironmentVariable returns the value for the specified environment for the current process.
In the case of your example, USERPROFILE is the path to the folder for the current user. For example, on my laptop USERPROFILE is C:\Users\Tim.
The output of System.IO.Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "Pictures") would be the USERPROFILE path plus "Pictures" - to continue with my example, it would be C:\Users\Tim\Pictures - which is the physical path to the My Pictures folder for my user account.
To get a list of all your environment variables, if you're curious, go to the DOS prompt and type in SET and hit return.
To answer your original question, you need to handle the FileSystemWatcher.Changed Event.
For example:
Private Shared Sub OnChanged(source As Object, e As RenamedEventArgs)
' Do your copy here
End Sub
You can hook the event handler up to your FileWatcher like this:
AddHandler fsw.Changed, AddressOf OnChanged
However, pay attention to this warning from the MSDN docs.
Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.
This is the code I used it does what I want it to.
Option Explicit On
Option Strict On
Imports System.IO
Imports Microsoft.VisualBasic ' I don't know this one, but the code worked without this.
Imports System.Security.Permissions ' I don't know the exactly what this does but the
' http://msdn.microsoft.com/ site did this, I assume it'f to allow for permission to
' watch files? The code still worked for me without this
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
Dim directoryPath As String = Path.GetDirectoryName(TextBox1.Text)
Dim varFileSystemWatcher As New FileSystemWatcher()
varFileSystemWatcher.Path = directoryPath
varFileSystemWatcher.NotifyFilter = (NotifyFilters.LastWrite)
varFileSystemWatcher.Filter = Path.GetFileName(TextBox1.Text) ' I know this
' limits what's being watched to one file, but right now this is
' what I want the program to do.
AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged
varFileSystemWatcher.EnableRaisingEvents = True
End Sub
Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
End Sub