Neo4j assembly error when running through Revit software - c#

I am building an addin for Autodesk's Revit.
I created a class library .NET Framework project called neo4jTest.
I'm trying to use the Neo4j API to communicate with the DB and save data to it.
I try to following:
Compile the .dll, and place the .dll file and .addin file in the
Revit folder which can load it.
Start Revit, and click "Load Once" for the addin approval.
Click the Button I've created (in the appropriate Tab)
Error appears (image below)
Run the .dll directly from the add-in manager -> no error.
Click the Button again -> no error.
I am expecting:
No error, and that both the direct execution of the .dll, and the Button, to perform the same action.
Neo4j DB logs the data being sent. Currently I can't see the data is actually being updated.
Is Revit first loading the assembly locally or caching it in some way when I run the .dll directly and then the loaded addin can use it because it is available?
Would appreciate any advice on how to go around this or solve it.
Thank you
CODE:
Main.cs file for button creation:
using Autodesk.Revit.UI;
using System.Reflection;
namespace neo4jTest
{
public partial class Main : IExternalApplication
{
public UIControlledApplication _application;
public Result OnStartup(UIControlledApplication application)
{
_application = application;
string tabName = "Neo4jTest";
application.CreateRibbonTab(tabName);
RibbonPanel ribbonPanel = application.CreateRibbonPanel(tabName, "Neo4jTest");
string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
InitializeButtons(ribbonPanel, thisAssemblyPath);
return Result.Succeeded;
}
private void InitializeButtons(RibbonPanel ribbonPanel, string thisAssemblyPath)
{
CreateButton(ribbonPanel, "neo4jTest", "neo4jTest", thisAssemblyPath, "neo4jTest.Class1", "neo4jTest");
}
public Result OnShutdown(UIControlledApplication application)
{
return Result.Succeeded;
}
private void CreateButton(
RibbonPanel ribbonPanel,
string name,
string text,
string thisAssemblyPath,
string className,
string toolTip
)
{
PushButtonData buttonData = new PushButtonData(name, text, thisAssemblyPath, className);
PushButton pushButton = ribbonPanel.AddItem(buttonData) as PushButton;
pushButton.ToolTip = toolTip;
}
}
}
Neo4jConnection.cs for establishing DB data:
using Autodesk.Revit.UI;
using Neo4j.Driver;
using System;
using System.Threading;
namespace neo4jTest
{
public static class Neo4jConnection
{
public static IDriver _driver;
private static readonly string uri = "myUri";
private static readonly string user = "myUser";
private static readonly string password = "myPassword";
public static void SaveToDB()
{
try
{
_driver = GraphDatabase.Driver(uri, AuthTokens.Basic(user, password));
var session = _driver.AsyncSession();
var data = session.ExecuteWriteAsync(async tx =>
{
var result = await tx.RunAsync("CREATE (n:Testing) " +
"SET n.fulltext = testing text " +
"SET n.username = userTest " +
"RETURN n"
);
return await result.ToListAsync();
});
Thread.Sleep(500);
session.Dispose();
}
catch (Exception ex)
{
TaskDialog.Show("Error", ex.StackTrace);
}
}
}
}
Class1 for executing button:
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
namespace neo4jTest
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class Class1 : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
Neo4jConnection.SaveToDB();
TaskDialog.Show("Validate", "Done");
return Result.Succeeded;
}
}
}

Related

Adding solution items from VisualStudio extension

I am making a test VisualStudio extension that adds new items to solution. I found the way of doing it through trial and error, but I am not satisfied with the complexity of this solution and have some opened questions.. Here is the code:
using EnvDTE;
using EnvDTE80;
//..
namespace MyFirstExtension
{
internal sealed class AddFileToSolution
{
public const int CommandId = PackageIds.CmdCreateVaultHelperConfig;
public static readonly Guid CommandSet = new Guid(PackageGuids.guidVaultHelperVsExtensionPackageCmdSetString);
private readonly AsyncPackage package;
private AddFileToSolution(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}
public static AddFileToSolution Instance { get; private set; }
private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider { get { return this.package; } }
public static async Task InitializeAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
Instance = new AddFileToSolution(package, commandService);
}
private async void Execute(object sender, EventArgs e)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
string fileFullPath = #"C:\Users\myusr\source\repos\VsExtensionTesting2\0.txt";
var dte = await package.GetServiceAsync(typeof(DTE));
var dte2 = dte as DTE2;
var solution2 = (Solution2)dte2.Solution;
// following line throws Exception!
//solution2.AddFromFile(fileFullPath);
bool isSolutionItemsFolderExists = IsSolutionItemsFolderExists(solution2);
if (!isSolutionItemsFolderExists)
{
solution2.AddSolutionFolder("Solution Items");
}
AddToSolutionItems(solution2, fileFullPath);
}
public bool IsSolutionItemsFolderExists(Solution2 solution2)
{
foreach (var solutionItemObj in solution2)
{
var solutionItem = solutionItemObj as Project;
string name = solutionItem.Name;
if (name == "Solution Items")
{
return true;
}
}
return false;
}
public void AddToSolutionItems(Solution2 solution2, string fileFullPath)
{
foreach (var solutionItemObj in solution2)
{
var solutionItem = solutionItemObj as Project;
string name = solutionItem.Name;
if (name == "Solution Items")
{
solutionItem.ProjectItems.AddFromFile(fileFullPath);
}
}
}
}
}
Here I am using the Visual Studio 2019 VSIX project template. It uses AsyncPackage by default. What I am trying to accomplish is to execute command Project.AddExistingItem on solution level.
Questions are:
In my solution - why does calling the command solution2.AddFromFile(fileFullPath); directly throws exception?
System.Runtime.InteropServices.COMException: 'The template specified cannot be found. Please check that the full path is correct.'
The file 100% exists in the directory.. Ideally I want to avoid creating Solution Items folder myself and let the API handle it..
When iterating the solution items/projects I am casting the items to type Project, however, during the debugging I can see that the item is also represented by type System.__ComObject and Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject however it's not possible to cast the item to either of the 2 types.. What are these other 2 types (or the correlation between 3 types) and how do I know exactly which type I am supposed to cast the solution items to?
What is the use-case for finding the commands executed by VS? I have downloaded the command explorer and captured the Project.AddExistingItem command, but how do I actually use this knowledge to execute the command from code? Here is how the command looks like:
I would be glad to hear some explanations about the questions I mentioned above.. My experience with the VS extension building so far - it's lots of googling and trial and error.. (as opposed to knowing actually what to do :) )

C# Class in a restricted AppDomain inherit from an other class located in the main AppDomain

I try to make a simple console modding project in C# where I have my program that contain a list of an abstract class named ElementInGame. I want to be able to create others class that inherit ElementInGame from a .txt file. The class ElementInGame will contain some basic methods (virtual and not virtual). But I don't want these other modded class execute malicious code, I would like that they can only access the methods/properties from the inherited class. Here is my ElementInGame code :
(My C# program #1)
using System;
namespace Modding
{
//The class itself inherit from MarshalByRefObject to be available in 2 differents Domains
public abstract class ElementInGame : MarshalByRefObject
{
public ElementInGame()
{
Console.WriteLine("ElementInGame class created");
}
public virtual int GetNumber()
{
return 10;
}
public void CountToTen()
{
for (int i = 0; i <= 10; i++)
{
Console.WriteLine(i);
}
}
}
}
Then I have my .txt file stored at "C:\program.txt"
(My original .txt file)
using System;
namespace Test
{
public class HelloWorld
{
public HelloWorld()
{
Console.WriteLine("Called Constructor() !");
}
public static int TestMethod()
{
Console.WriteLine("Called TestMethod() !");
return 11;
}
}
}
So I code the main program to read the .txt file, compile it with restrictions, and execute it :
(My C# program #2 in a second .cs file, long code warning)
using System;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
using System.Security.Permissions;
using System.Security;
using System.Security.Policy;
using System.Runtime.Remoting;
using System.Collections.Generic;
namespace Modding
{
public class Program : MarshalByRefObject
{
public static void Main(string[] args)
{
string assemblyPath = #"C:\program.txt"; // Where the .txt file is stored
string code = File.ReadAllText(assemblyPath); //The code to compile
CompilerResults compile = CompileFromCode(code); //Compile the code in the temporary files
string fullPath = compile.PathToAssembly; //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp\5v2p3qki.dll
string pathWithoutFile = Path.GetDirectoryName(fullPath); //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp
string pathNameOnly = Path.GetFileNameWithoutExtension(fullPath); //sample : 5v2p3qki
Program newDomainInstance = GetOtherProtectedDomainInstance(pathWithoutFile);
newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld");
List<ElementInGame> allElement = new List<ElementInGame>();
//allElement.Add ***?***
Console.ReadKey();
}
public static Program GetOtherProtectedDomainInstance(string pathWithoutFile)
{
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = pathWithoutFile;
//Set some permissions to avoid malicious code
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
StrongName fullTrustAssembly = new StrongName(
new StrongNamePublicKeyBlob(typeof(Program).Assembly.GetName().GetPublicKey()),
typeof(Program).Assembly.GetName().Name,
typeof(Program).Assembly.GetName().Version);
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Program).Assembly.ManifestModule.FullyQualifiedName,
typeof(Program).FullName
);
Program newDomainInstance = (Program)handle.Unwrap();
return newDomainInstance;
}
public static CompilerResults CompileFromCode(string code)
{
//Compile the code in a .dll locate in the temporary files
//The following code is based on https://stackoverflow.com/questions/10314815/trying-to-compile-and-execute-c-sharp-code-programmatically
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = false;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
//Adding a reference to the current project to allow the .txt file to inherit the class "ElementInGame" later
string[] references = { "System.dll", Assembly.GetEntryAssembly().Location };
CompilerParams.ReferencedAssemblies.AddRange(references);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);
if (compile.Errors.HasErrors)
{
string text = "Compile error: ";
foreach (CompilerError ce in compile.Errors)
{
text += "rn" + ce.ToString();
}
throw new Exception(text);
}
return compile;
}
public static void DisplaySomething()//Useful for later
{
Console.WriteLine("This isn't supposed to be display");
}
//Calling a method from the restricted Domain
public void CallMethod(string assemblyName, string typeName, string entryPoint, object objectToExecute = null, object[] parameters = null)
{
MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
try
{
target.Invoke(objectToExecute, parameters);
}
catch
{
Console.WriteLine("Security Error with Method " + assemblyName + " namespace : " + typeName + " method : " + entryPoint);
}
}
//Create an instance from the restricted Domain
public void CreateObject(string assemblyName, string typeName)
{
try
{
object o = Assembly.Load(assemblyName).CreateInstance(typeName);
}
catch
{
Console.WriteLine("Security Error with Constructor " + assemblyName + " namespace : " + typeName);
}
}
}
}
For the moment the .txt file don't have any link at all with my C# program. The code work properly and I got the following output :
Called TestMethod() !
Called Constructor() !
Then I edit my code in my .txt file to inherit from Modding.ElementInGame :
(My edited .txt file)
using System;
namespace Test
{
public class HelloWorld : Modding.ElementInGame
{
public HelloWorld() : base()
{
Console.WriteLine("Called Constructor() !");
}
public static int TestMethod()
{
Console.WriteLine("Called TestMethod() !");
return 11;
}
}
}
So I expected an output like :
Called TestMethod() !
ElementInGame class created
Called Constructor() !
But after this change, the program crash with a System.NullReferenceException at when calling the TestMethod : newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
However creating an instance of HelloWorld (the .txt file): newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld"); seem to works (no crash, the code stay in the try part when doing the try/catch), but my there is nothings print in my console, so it doesn't work I guess ?
Changing the permission of the AppDomain change nothing.
PermissionSet permSet = new PermissionSet(PermissionState.Unrestricted);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags));
So my question is : How can I create and store an instance of the .txt file in my program that inherit from ElementInGame (and add it to the list of ElementInGame) ?
That way I can use from my program the virtual method GetNumber(). I don't want the .txt file have access to the program itself (like calling the method DisplaySomething()), just communicate with ElementInGame.
You are generating and loading reference assemblies from different locations. You did set the current directory for output but forgot to assign it to compiler parameters.
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.OutputAssembly = Path.Combine(outputDirectory, "Test.dll");
This should fix the issue.

Why Can't I call this method?

I'm trying to call a method from another class within a service, however it's saying that the method I'm trying to call doesn't exist and would like some help if possible.
the program is a work project, which logs user inactivity as we've had issues with people not picking up the phone, code is below, this is a topshelf service that consumes messages from rabbitMQ and I want it to consume the messages and forward them to a database =]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using NLog;
using IWshRuntimeLibrary;
using Topshelf;
using System.Data.Odbc;
using EasyNetQ;
using RabbitMQ;
using EasyNetQ.Topology;
using System.Threading.Tasks;
using System.Windows.Forms;
using AccessEye;
namespace LogService
{
public class WindowsServiceHost : ServiceControl, ServiceShutdown
{
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public bool Start(HostControl hostControl)
{
Program.bus = RabbitHutch.CreateBus("host=as01.access.local;virtualHost=DEV-Reece;username=reece;password=reece").Advanced;
//var bus = RabbitHutch.CreateBus("host=as01.access.local;virtualHost=DEV-Reece;username=reece;password=reece").Advanced;
var queue = Queue.Declare(true, false, true, null);
var exchange = Exchange.DeclareFanout("UserActivityFanout", true, false, null);
var exchangeTopic = Exchange.DeclareTopic("UserActivity", true, false, null);
queue.BindTo(exchange, "#");
exchange.BindTo(exchangeTopic, "#");
Program.bus.Subscribe<AccessEye.LogData>(queue, (msg, messageRecInfo) => Task.Factory.StartNew(() =>
{
WriteLogDataToDb();
Console.WriteLine(msg.Body.UserName + " -- " + msg.Body.ComputerName + " -- " + msg.Body.EventType + " -- " + msg.Body.TeamviewerId);
}));
return true;
}
And this is the method I'm trying to call
public partial class AppForm : Form
{
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private Screensaver watcher;
public Inactivity inactivity;
IAdvancedBus bus;
IExchange exchange;
public void WriteLogDataToDb(LogData data)
{
using (var db = new LogService.UserActivityDataContext())
{
DbLogData logData = AutoMapper.Mapper.Map<LogData, DbLogData>(data);
int t = (int)data.EventType;
EventType eventType = db.EventTypes.FirstOrDefault(r => r.Id == t);
if (eventType == null)
{
eventType = db.EventTypes.Add(new EventType
{
Event = GetEnumDescriptionAttributeValue(data.EventType),
Id = (int)data.EventType
});
db.SaveChanges();
}
logData.EventTypeId = eventType.Id;
db.LogEvents.Add(logData);
db.SaveChanges();
}
}
If your class with the WriteLogDataToDb() declared is called ClassA, then do two things. Make the method static, and you actually have to pass some LogData data through it.
public class AppForm
{
public static void WriteLogDataToDb(LogData data)
{
using (var db = new LogService.UserActivityDataContext())
{
DbLogData logData = AutoMapper.Mapper.Map<LogData, DbLogData>(data);
int t = (int)data.EventType;
EventType eventType = db.EventTypes.FirstOrDefault(r => r.Id == t);
if (eventType == null)
{
eventType = db.EventTypes.Add(new EventType
{
Event = GetEnumDescriptionAttributeValue(data.EventType),
Id = (int)data.EventType
});
db.SaveChanges();
}
logData.EventTypeId = eventType.Id;
db.LogEvents.Add(logData);
db.SaveChanges();
}
}
}
Then in your Start code, you have to call AppForm.WriteLogDataToDb(data)
Edit:
Now that these classes are in two different projects, you need to add reference so your WindowsServiceHost can use AppForm. To do this:
Right-click > Properties on the project containing AppForm. On the Application tab, take note of the Assembly name:
Right-click the References item in WindowsServiceHost and choose Add reference
Go to the Projects tab
Add the Assembly name: noted in step #1
Right click AppForm in WindowsSerivceHost and Resolve by adding your using statement.

Dynamic load assembly in WinRT

I have created a program to load dynamic assemblies using the following code:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
namespace BarcodeReader
{
public class Parsing
{
private static string _FolderName = "BarcodeReaders";
private static bool _Initialized = false;
private static IEnumerable<IBarcodeReader> _Objs;
/// Parse the picture
/// <returns>The value from the picture</returns>
public static async Task<string> ParsePicture()
{
// Check if this class has not been initialized, and if it hasn't initialize it
if (!_Initialized)
{
await InitializeAsync();
}
foreach (var Obj in _Objs)
{
if (Obj.IsType())
{
return Obj.GetValue();
}
}
return null;
}
private static async Task InitializeAsync()
{
// Get the folder
var Folder = await GetFolder();
// Get the Files in the Folder
var Files = await Folder.GetFilesAsync();
// Initialize the objects and set them
_Objs = InitializeObjects(Files);
// Set it as initialized
_Initialized = true;
}
private static IEnumerable<IBarcodeReader> InitializeObjects(IEnumerable<Windows.Storage.StorageFile> Files)
{
foreach (var File in Files)
{
string Name = File.Path;
var Assembly = System.Reflection.Assembly.Load(new AssemblyName(Name));
foreach (var Typ in Assembly.ExportedTypes)
{
var TypInfo = Typ.GetTypeInfo();
foreach (var Interf in TypInfo.ImplementedInterfaces)
{
if (Interf.Name.Equals("IBarcodeReader"))
{
yield return (IBarcodeReader)Activator.CreateInstance(Typ);
}
}
}
}
}
private static async Task<bool> BarcodeFolderExist(Windows.Storage.StorageFolder Folder)
{
// Get all folders
var Folders = await Folder.GetFoldersAsync();
// For each folder, check if it is the Folder we are searching and if it is return true
foreach (var Foldr in Folders)
{
if (Foldr.Name.Equals(_FolderName))
{
return true;
}
}
// Return false as the folder was not found
return false;
}
private static async Task<Windows.Storage.StorageFolder> GetFolder()
{
// Get the local-folder
var Folder = Windows.Storage.ApplicationData.Current.LocalFolder;
// Check if the folder does not exist, and if it does not create it
if (!await BarcodeFolderExist(Folder))
{
await Folder.CreateFolderAsync(_FolderName);
}
return await Folder.GetFolderAsync(_FolderName);
}
}
}
And the project I am trying to load is these files
namespace QRReader
{
public sealed class QRReader : IBarcodeReader
{
public bool IsType()
{
return true;
}
public string GetValue()
{
return "HEJ";
}
}
public interface IBarcodeReader
{
bool IsType();
string GetValue();
}
}
But I get this error
FileLoadException was unhandled by user code
The assembly name or code base was illegal. (Exception HRESULT: 0x80131047)
The name-variable is set to
C:\Users\Lasse\AppData\Local\Packages\93e3b2c9-7ef8-4537-be39-d0f3e93ca100_e85ydygyad1dy\LocalState\BarcodeReaders\QRReader.winmd
Everything I've read on the internet says that Microsoft have made it a deliberate security feature of the runtime environment (WinRT, and UWP) that it be impossible to load assemblies at runtime. This is a show stopping limiting feature in UWP. It more or less renders the platforms useless because if there are customisations for a given customer, the application vendor would have to split the app and deploy it a version to the store for each customer.
Please take the time to up vote this feature request on allowing assemblies to be loaded dynamically at runtime:
https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/18145291-dynamically-load-assembly

Change update URL for a ClickOnce application

Is it possible to change the update URL to different location of an installed ClickOnce application? If so, how can I do that?
You mention in your comment that you wish to change it "on the client side". This is not possible. Your client app must be able to check for the update at the previous location which will then redirect it to the new location for the immediately next deployment.
See How to move a ClickOnce deployment.
Is it possible with a trick.
You can deploy it to the default publish location. (the application shouldn't check for updates).
Then copy your deployment to the customers server.
Just install your application on the client machines.
The field System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri contains the location and .application where the application is installed from. If you know that, then you can simple execute this url.
To check if there is an update, examine the .application file en grape the version.
this is my helper class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
namespace MatemanSC.Utility
{
public class ClickOnceUtil
{
Version _UpdateVersion = null;
public string UpdateLocation
{
get
{
return System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri;
}
}
public Version AvailableVersion
{
get
{
if (_UpdateVersion == null)
{
_UpdateVersion = new Version("0.0.0.0");
if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
{
using (XmlReader reader = XmlReader.Create(System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri))
{
//Keep reading until there are no more FieldRef elements
while (reader.ReadToFollowing("assemblyIdentity"))
{
//Extract the value of the Name attribute
string versie = reader.GetAttribute("version");
_UpdateVersion = new Version(versie);
}
}
}
}
return _UpdateVersion;
}
}
public bool UpdateAvailable
{
get
{
return System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion != AvailableVersion;
}
}
public string CurrentVersion
{
get
{
return System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
}
}
public void Update()
{
System.Diagnostics.Process.Start(System.Deployment.Application.ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri);
Environment.Exit(0);
}
public void CheckAndUpdate()
{
try
{
if (UpdateAvailable)
Update();
}
catch (Exception)
{
}
}
}
}
And this how to use it:
public partial class App : Application
{
public App()
{
ClickOnceUtil clickonceutil = new ClickOnceUtil();
clickonceutil.CheckAndUpdate();
}
}
When you want to change the url that you will use to upgrade programs, you can just use url rewrite at web.config: the old program will point to the old url, but it will bring the new program, which will have the new url.

Categories

Resources