I am trying to write a C# program that will automate a user's input into the SAP GUI (currently on version 7400.3.11.3364) using SAP's scripting API. I've done similar things in the past using VBA, but I'm struggling to get it working exactly how I want it to in C#. My end goal is to have a method that opens SAP (if it isn't already running), returns the GuiApplication object of SAP, and leaves SAP open after my program ends. I currently have it working in VBA, and I believe I had it working correctly in C# on a previous project when we were on SAP GUI version 7.3, but I'm not 100% sure
Here is the VBA Function I use:
Public Function GetSapApp() as GuiApplication
Dim Start as Date
If GetObject("SAPGUI") Is Nothing Then
Start = Now()
Shell "C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe"
Do Until Not GetObject("SAPGUI") Is Nothing Or Now > (Start + TimeValue("00:01:00"))
DoEvents
Loop
If GetObject("SAPGUI") Is Nothing Then
MsgBox "Unable to detect SAP Scripting API. Please contact the developer."
End If
Set GetSapApp = GetObject("SAPGUI").GetScriptingEngine
End Function
And here are the different C# methods I've found while googling:
Below is what I'm currently using (the idea was inspired by this: https://www.codeproject.com/Questions/829500/How-do-I-connect-Csharp-to-SAP-GUI), but there are a couple issues with it. It doesn't seem to detect SAP if it is already open. Also, after my program runs, this SAP isn't detectable by any of our VBA macros.
private static GuiApplication GetSapApp()
{
try
{
object SapGuilRot = new CSapROTWrapper().GetROTEntry("SAPGUI");
return SapGuilRot.GetType().InvokeMember("GetScriptingEngine", System.Reflection.BindingFlags.InvokeMethod, null, SapGuilRot, null) as GuiApplication;
}
catch (Exception e)
{
try
{
string SapPath = "C:\\Program Files (x86)\\SAP\\FrontEnd\\SAPgui\\saplogon.exe";
if (File.Exists(SapPath))
{
System.Diagnostics.Process.Start(SapPath);
DateTime StartTime = DateTime.Now;
object SapGuilRot = new CSapROTWrapper().GetROTEntry("SAPGUI");
while (SapGuilRot == null && 30 >= (DateTime.Now - StartTime).TotalSeconds)
{
SapGuilRot = new CSapROTWrapper().GetROTEntry("SAPGUI");
}
return SapGuilRot.GetType().InvokeMember("GetScriptingEngine", System.Reflection.BindingFlags.InvokeMethod, null, SapGuilRot, null) as GuiApplication;
}
else
{
return null;
}
}
catch
{
return null;
}
}
}
Here is another option I've tried (per this answer on another SO post: https://stackoverflow.com/a/14205520/5134861), but it has the same first issue as above (doesn't detect if SAP is currently running), the SAP session that gets opened when you call the OpenConnection method looks different and then closes when my program is done running.
private static GuiApplication GetSapApp()
{
return (GuiApplication)System.Activator.CreateInstance(Type.GetTypeFromProgID("SapGui.ScriptingCtrl.1"));
}
And here is the last option I've tried, but I get a Cannot create ActiveX component error, despite having varified sapfewse.ocx is registered.
private static GuiApplication GetSapApp()
{
object sap = Microsoft.VisualBasic.Interaction.GetObject("SAPGUI", "");
return sap.GetType().InvokeMember("GetScriptingEngine", System.Reflection.BindingFlags.InvokeMethod, null, sap, null) as GuiApplication;
}
Any help and/or suggestions would be greatly appreciated.
Thanks in advance!
I've used:
Microsoft.VisualBasic.Interaction.GetObject("SAPGUISERVER", "");
Related
I am creating a project automation tool in Visual Studio 2013 where I have my own project template and I am trying to add it to an existing solution programatically.I am using the following code in a console application.
EnvDTE.DTE dte = (EnvDTE.DTE)Marshal.GetActiveObject("VisualStudio.DTE.12.0");
string solDir = dte.Solution.FullName;
solDir=solDir.Substring(0, solDir.LastIndexOf("\\"));
dte.Solution.AddFromTemplate(path, solDir+"\\TestProj", "TestProj", false);
It is working when I run the application from Visual Studio IDE. But when I try to run the exe from command prompt, I get the following exception.
Unhandled Exception: System.Runtime.InteropServices.COMException: Operation unav
ailable (Exception from HRESULT: 0x800401E3 (MK_E_UNAVAILABLE))
at System.Runtime.InteropServices.Marshal.GetActiveObject(Guid& rclsid, IntPtr reserved, Object& ppunk)
at System.Runtime.InteropServices.Marshal.GetActiveObject(String progID)
at ProjectAutomation.Console.Program.Main(String[] args)
I want to know whether there is any way to get the active EnvDTE.DTE instance outside Visual Studio IDE .?
Automating an existing Visual Studio instance from an external tool to modify a loaded solution is a bad idea. If you use GetActiveObject(...) and there are two Visual Studio instances launched, how do you know that the correct instance is returned? And what if the user or Visual Studio is doing something with the solution when the user launches the external tool? There are two better approaches:
1) Use an external tool to automate a new Visual Studio instance, load the desired solution and modify it. This can be done even with the VS instance not visible. To create a new instance the proper code is:
System.Type type = Type.GetTypeFromProgID("VisualStudio.DTE.12.0");
EnvDTE.DTE dte = (EnvDTE.DTE) System.Activator.CreateInstance(type);
dte.MainWindow.Visible = true;
...
2) Use a Visual Studio extension, such as a macro (VS 2010 or lower), add-in (VS 2013 or lower) or package (any VS version) to provide a menu item or button toolbar that, when clicked, modifies the currently loaded solution. This prevent the "busy" scenario because if VS is busy the menu item or toolbar button can't be clicked (unless the "busy" operation is asynchronous).
I found an alternative to GetActiveObject, here, where Kiril explains how to enumerate the ROT. There are other examples on MSDN.
Since some SO users don't like links here are the details:
Enumerate all of the processes, named devenv.exe.
Show a list of main window titles. (I strip "Microsoft Visual Studio" off the end)
Ask the user which one they want to use.
Use the process.Id to find an object in the ROT, which I believe is the OP's question. This is done by enumerating the ROT using IEnumMoniker.Next(), which returns monikers and process id's (in the case of VS).
Having found the moniker. Cast the running object to a DTE and off you go.
COM, ROT and Moniker sounded too complex for me, so I was happy to see that the heavy lifting had already been done at the link above.
I had the example working in a couple of minutes. It worked the first time I stepped through with the debugger. But at full speed, I needed to add some sleeps or retries, because it is easy to get an exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))
Also, I replaced the exact match with a regex that tolerates other versions of VS:
Regex monikerRegex = new Regex(#"!VisualStudio.DTE\.\d+\.\d+\:" + processId, RegexOptions.IgnoreCase);
The issue where VS may be busy, with an open dialog, or compiling many projects is common to many applications you might try to force feed key strokes or COM requests. If you get an error retry for a few seconds. Finally, pop up a message box if needed.
Some SO users don't like links because links get broken ;)
Took me over an hour to write my version so might as well post it here. Requires references to envdte and envte80 (from add references / assemblies / extensions). Provides static method for opening a C# file in Visual Studio or Notepad++ as a backup and optionally navigate to a specific line.
using System;
using System.Diagnostics;
using EnvDTE80;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.InteropServices;
namespace whatever
{
public static class CsFile
{
public static void Open(string fileName, int? lineNr = null)
{
try
{
OpenFileInVisualStudio(fileName, lineNr);
}
catch
{
try
{
OpenFileInNotePadPlusPlus(fileName, lineNr);
}
catch
{
// Woe is me for all has failed. Somehow show an error.
}
}
}
public static void OpenFileInVisualStudio(string fileName, int? lineNr = null)
{
DTE2 dte = null;
TryFor(1000, () => dte = GetDteByName("VisualStudio.DTE"));
if (dte == null) throw new Exception("Visual Studio not running?");
dte.MainWindow.Activate();
TryFor(1000, () => dte.ItemOperations.OpenFile(fileName));
if (lineNr.HasValue) TryFor(1000, () => ((EnvDTE.TextSelection)dte.ActiveDocument.Selection).GotoLine(lineNr.Value, true));
}
public static void OpenFileInNotePadPlusPlus(string fileName, int? lineNr = null)
{
if (lineNr.HasValue) fileName += " -n" + lineNr.Value.ToString();
Process.Start(#"C:\Program Files (x86)\Notepad++\notepad++.exe", fileName);
}
private static void TryFor(int ms, Action action)
{
DateTime timeout = DateTime.Now.AddMilliseconds(ms);
bool success = false;
do
{
try
{
action();
success = true;
}
catch (Exception ex)
{
if (DateTime.Now > timeout) throw ex;
}
} while (!success);
}
static DTE2 GetDteByName(string name)
{
IntPtr numFetched = Marshal.AllocHGlobal(sizeof(int));
IRunningObjectTable runningObjectTable;
IEnumMoniker monikerEnumerator;
IMoniker[] monikers = new IMoniker[1];
IBindCtx bindCtx;
Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
bindCtx.GetRunningObjectTable(out runningObjectTable);
runningObjectTable.EnumRunning(out monikerEnumerator);
monikerEnumerator.Reset();
while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
{
IBindCtx ctx;
CreateBindCtx(0, out ctx);
string runningObjectName;
monikers[0].GetDisplayName(ctx, null, out runningObjectName);
if (runningObjectName.Contains(name))
{
object runningObjectVal;
runningObjectTable.GetObject(monikers[0], out runningObjectVal);
DTE2 dte = (DTE2)runningObjectVal;
return (dte);
}
}
return null;
}
[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);
}
}
I am new here and I hope that i will find a solution for my problem. The background of the problem is as follows:
I am trying to build an expert system that constitute a C# front-end which is interacting with Swi-prolog.
I have downloaded SwiPlCs.dll (A CSharp class library to connect .NET languages with Swi-Prolog)
And added a reference to it in a Visual Studio project(Win form app) that I have created to test if I can query prolog from c# (I followed the example used in the documentation found here).
It worked fine.
Then, in a more complicated scenario, I have built a WCF service that will act as an intermediary layer between Swi-Prolog and C# client application (it consumes the service).
The service is hosted in IIS 7.0.
For the sake of simplicity, lets say my service contains three methods.
The first method initializes the prolog engine, consults prolog source file then queries the file.
The second method performs another query.
The third method calls PlCleanup().
Method#1:
public void LaunchAssessment()
{
Dictionary<string, string> questions = new Dictionary<string, string>();
#region : Querying prolog using SwiPlCs
try
{
if (!PlEngine.IsInitialized)
{
String[] param = { "-q" };
PlEngine.Initialize(param);
PlQuery.PlCall("consult('D:/My FYP Work/initialAssessment')");
using (var q = new PlQuery("go(X, Y)"))
{
foreach (PlQueryVariables v in q.SolutionVariables)
{
questions.Add("name", v["X"].ToString());
questions.Add("age", v["Y"].ToString());
}
}
}
}
catch (SbsSW.SwiPlCs.Exceptions.PlException exp)
{
throw new FaultException<PrologFault>(new PrologFault(exp.Source), exp.MessagePl);
}
#endregion
Callback.PoseQuestion(questions, ResponseType.None);
}
Method#2:
public void DetermineAgeGroup(int age)
{
//Determine age group
string age_group = string.Empty;
try
{
using (var query = new PlQuery("age_group(" + age + ", G)"))
{
foreach (PlQueryVariables v in query.SolutionVariables)
age_group += v["G"].ToString();
}
}
catch (SbsSW.SwiPlCs.Exceptions.PlException exp)
{
throw new FaultException<PrologFault>(new PrologFault(exp.Source), exp.MessagePl);
}
//Check whether age_group is found or not
if (string.IsNullOrEmpty(age_group))
{
throw new FaultException<NoSolutionFoundFault>(new NoSolutionFoundFault("No solution found"), "Age specified exceeds the diagnosis range!");
}
else
{
Callback.RespondToUser(age_group, ResponseType.Age);
}
}
Method#3:
public void QuitProlog()
{
if (PlEngine.IsInitialized)
{
PlEngine.PlCleanup();
}
}
The client invokes the first method just fine and a result of the first query is successfully returned. When client tries to call the second method an exception is thrown with message (attempted to read or write protected memory) which causes the application to freeze. I checked the event viewer and this is what I get:
Application: w3wp.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.AccessViolationException
Stack:
at SbsSW.SwiPlCs.SafeNativeMethods.PL_new_term_ref()
at SbsSW.SwiPlCs.PlQuery..ctor(System.String, System.String)
at SbsSW.SwiPlCs.PlQuery..ctor(System.String)
at PrologQueryService.PrologQueryService.DetermineAgeGroup(Int32)
I also tried to use the interface for a .NET project.
Looking in the official repository of the CSharp interface to SWI-Prolog I noticed that the project is very old and the latest updates do not seem included in the binaries available in the download page of the official website.
Then I did the following steps:
The contrib repository dedicated to .NET indicates that the compatible SWI-Prolog version (at the time of writing) is "8.0.3-1" (look in the README file).
-> Then I uninstalled from my computer the latest stable and installed the indicated one. I got it from the full list of downloads of the old versions at this link.
I cloned the SWI-Prolog/contrib-swiplcs repository, unloaded the incompatible projects from the solution, in my case, since I don't use Visual Studio.
-> I set the target framework to Net Framework 4.8 and recompiled it (you can also do this with standard NET). Beware of some pragma directives defined in the old project file (For example I re-defined _PL_X64 variable via code.
I brought the main unit test methods into a new project with xUnit wiht the appropriate changes.
I set the target to x64, recompiled and rebuilt the tests and the "hello world" example.
It worked!
I was able to use SWI-Prolog both for Net 4.8 and in other Net Core applications (if you make the needed changes in order to target the Net Standard). You should not have any problem in both cases).
This is my fork as a preliminary example.
Finally, I can load a *.pl Prolog file with a program in my C# application and use it to evaluate some business logic rules (example with boolean answer [Permitted/Not-Permitted]):
[Fact]
public void ShouldLoadAProgramAndUseIt()
{
var pathValues = Environment.GetEnvironmentVariable("PATH");
pathValues += #";C:\Program Files\swipl\bin";
Environment.SetEnvironmentVariable("PATH", pathValues);
// Positioning to project folder
var currentDirectory = Directory.GetCurrentDirectory().Split('\\').ToList();
currentDirectory.RemoveAll(r => currentDirectory.ToArray().Reverse().Take(3).Contains(r));
var basePath = currentDirectory.Aggregate((c1, c2) => $"{c1}\\{c2}");
var filePath = $"{basePath}\\prolog_examples\\exec_checker.pl";
String[] param = { "-q", "-f", filePath };
PlEngine.Initialize(param);
try
{
var query = "exutable('2020-08-15',[('monthly', ['2019-12-30', '2020-03-10'])])";
_testOutputHelper.WriteLine($"Query: {query}");
using (var q = new PlQuery(query))
{
var booleanAnswer = q.NextSolution();
_testOutputHelper.WriteLine($"Answer: {booleanAnswer}");
Assert.True(booleanAnswer);
}
query = "exutable('2020-08-15',[('daily', ['2019-12-30', '2020-08-15'])])";
_testOutputHelper.WriteLine($"Query: {query}");
using (var q = new PlQuery(query))
{
var booleanAnswer = q.NextSolution();
_testOutputHelper.WriteLine($"Answer: {booleanAnswer}");
Assert.False(booleanAnswer);
}
}
finally
{
PlEngine.PlCleanup();
}
}
Try to close engine in the end of the first method and initialize it in the second again.
You can check this as the answer to the question unless you object.
I am trying to prevent the user from pinning my .NET app to the taskbar. I've found some code on the Old New Thing that does just that. However, it is in C++.
#include <shellapi.h>
#include <propsys.h>
#include <propkey.h>
HRESULT MarkWindowAsUnpinnable(HWND hwnd)
{
IPropertyStore *pps;
HRESULT hr = SHGetPropertyStoreForWindow(hwnd, IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr)) {
PROPVARIANT var;
var.vt = VT_BOOL;
var.boolVal = VARIANT_TRUE;
hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
pps->Release();
}
return hr;
}
BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
MarkWindowAsUnpinnable(hwnd);
return TRUE;
}
I am having very little luck converting it to c#. Can someone help?
You can download the Windows API Code Pack which has the necessary p/invoke calls you need to translate the code in your post to C#.
Either use the library in whole or find the specific calls and definitions you require (search it for SHGetPropertyStoreForWindow and then its other dependencies).
In the question, the Old New Thing post also talks about how you can set some registry settings on per application basis that will so prevent the pinning an application to the taskbar.
All you have to do is add the value of "NoStartPage" to a key for your application under the Root\Applications. The value can be blank and of any type, if Windows just sees it is there it will not show the ability to pin the app, when the user right clicks on it in the taskbar.
Here is the documentation from Microsoft on this feature: Use Registry to prevent pinning of an application
The one caveat to this is that in Windows 7, due to UAC, you have to run as administrator to update the registry. I did this via the app.manifest.
The code to find the right and update the correct registry keys is below (hopefully it is not too verbose):
public static void Main(string[] args)
{
// Get Root
var root = Registry.ClassesRoot;
// Get the Applications key
var applicationsSubKey = root.OpenSubKey("Applications", true);
if (applicationsSubKey != null)
{
bool updateNoStartPageKey = false;
// Check to see if your application already has a key created in the Applications key
var appNameSubKey = applicationsSubKey.OpenSubKey("MyAppName.exe", true);
if (appNameSubKey != null)
{
// Check to see if the NoStartPage value has already been created
if (!appNameSubKey.GetValueNames().Contains("NoStartPage"))
{
updateNoStartPageKey = true;
}
}
else
{
// create key for your application in the Applications key under Root
appNameSubKey = applicationsSubKey.CreateSubKey("MyAppName.exe", RegistryKeyPermissionCheck.Default);
if (appNameSubKey != null)
{
updateNoStartPageKey = true;
}
}
if (updateNoStartPageKey)
{
// Create/update the value for NoStartPage so Windows will prevent the app from being pinned.
appNameSubKey.SetValue("NoStartPage", string.Empty, RegistryValueKind.String);
}
}
}
Using the WindowsAPICodePack (via NuGet) you need code resembling:
// Ensure the handle is available
new WindowInteropHelper(window).EnsureHandle();
// Prevent the window from being pinned to the task bars
var preventPinningProperty = new PropertyKey(
new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 9);
WindowProperties.SetWindowProperty(window, preventPinningProperty, "1");
this is what I'm currently doing:
protected void setupProject()
{
bool lbDone = false;
int liCount = 0;
while (!lbDone && liCount < pMaxRetries)
{
try
{
pProject.ProjectItems.Item("Class1.cs").Delete();
lbDone = true;
}
catch (System.Runtime.InteropServices.COMException loE)
{
liCount++;
if ((uint)loE.ErrorCode == 0x80010001)
{
// RPC_E_CALL_REJECTED - sleep half sec then try again
System.Threading.Thread.Sleep(pDelayBetweenRetry);
}
}
}
}
now I have that try catch block around most calls to the EnvDTE stuff, and it works well enough. The problem I have is when I to loop through a collection and do something to each item once.
foreach(ProjectItem pi in pProject.ProjectItems)
{
// do something to pi
}
Sometimes I get the exception in the foreach(ProjectItem pi in pProject.ProjectItems) line.
Since I don't want to start the foreach loop over if I get the RPC_E_CALL_REJECTED exception I'm not sure what I can do.
Edit to answer comment:
Yes I'm automating VS from another program and yes I usually am using VS for something else at the same time. We have an application that reads an xml file then generates around 50 VS solutions based on the xml file. This usually takes a couple of hours so I try to do other work while this is happening.
There is a solution on this MSDN page: How to: Fix 'Application is Busy' and 'Call was Rejected By Callee' Errors. It shows how to implement a COM IOleMessageFilter interface so that it will automatically retry the call.
First, Hans doesn't want to say so but the best answer to "how to do this" is "don't do this". Just use separate instances of visual studio for your automation and your other work, if at all possible.
You need to take your problem statement out somewhere you can handle the error. You can do this by using in integer index instead of foreach.
// You might also need try/catch for this!
int cProjectItems = pProject.ProjectItems.Length;
for(iProjectItem = 0; iProjectItem < cProjectItems; iProjectItem++)
{
bool bSucceeded = false;
while(!bSucceeded)
{
try{
ProjectItem pi = pProject.ProjectItems[iProjectItem];
// do something with pi
bSucceeded = true;
}catch (System.Runtime.InteropServices.COMException loE)
{
liCount++;
if ((uint)loE.ErrorCode == 0x80010001) {
// RPC_E_CALL_REJECTED - sleep half sec then try again
System.Threading.Thread.Sleep(pDelayBetweenRetry);
}
}
}
}
I didn't have much luck with the recommended way from MSDN, and it seemed rather complicated. What I have done is to wrap up the re-try logic, rather like in the original post, into a generic utility function. You call it like this:
Projects projects = Utils.call( () => (m_dteSolution.Projects) );
The 'call' function calls the function (passed in as a lambda expression) and will retry if necessary. Because it is a generic function, you can use it to call any EnvDTE properties or methods, and it will return the correct type.
Here's the code for the function:
public static T call<T>(Func<T> fn)
{
// We will try to call the function up to 100 times...
for (int i=0; i<100; ++i)
{
try
{
// We call the function passed in and return the result...
return fn();
}
catch (COMException)
{
// We've caught a COM exception, which is most likely
// a Server is Busy exception. So we sleep for a short
// while, and then try again...
Thread.Sleep(1);
}
}
throw new Exception("'call' failed to call function after 100 tries.");
}
As the original post says, foreach over EnvDTE collections can be a problem as there are implicit calls during the looping. So I use my 'call' function to get the Count proprty and then iterate using an index. It's uglier than foreach, but the 'call' function makes it not so bad, as there aren't so many try...catches around. For example:
int numProjects = Utils.call(() => (projects.Count));
for (int i = 1; i <= numProjects; ++i)
{
Project project = Utils.call(() => (projects.Item(i)));
parseProject(project);
}
I was getting the same error using C# to read/write to Excel. Oddly, it worked in debug mode but not on a deployed machine. I simply changed the Excel app to be Visible, and it works properly, albeit about twice as slow. It is annoying to have an Excel app open and close dynamically on your screen, but this seems to be the simplest work-around for Excel.
Microsoft.Office.Interop.Excel.Application oApp = new ApplicationClass();
oApp.Visible = true;
oApp.DisplayAlerts = false;
How can I get the number of times a program has previously run in C# without keeping a file and tallying. If it is not possible that way, can it be gotten from the Scheduled Task Manager?
To C. Ross: how would this be done in a registry setting? forgive me. . . what is a registry setting?
I do this in a registry setting.
static string AppRegyPath = "Software\\Cheeso\\ApplicationName";
static string rvn_Runs = "Runs";
private Microsoft.Win32.RegistryKey _appCuKey;
public Microsoft.Win32.RegistryKey AppCuKey
{
get
{
if (_appCuKey == null)
{
_appCuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(AppRegyPath, true);
if (_appCuKey == null)
_appCuKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(AppRegyPath);
}
return _appCuKey;
}
set { _appCuKey = null; }
}
public int UpdateRunCount()
{
int x = (Int32)AppCuKey.GetValue(rvn_Runs, 0);
x++;
AppCuKey.SetValue(rvn_Runs, x);
return x;
}
If it's a WinForms app, you can hook the Form's OnClosing event to run UpdateCount.
To the best of my knowledge Windows does not keep this information for you. You would have to tally the value somewhere (file, database, registry setting). The Windows Task Scheduler is very low functionality.
The number of time an app has run is stored in the registry; there are a couple of caveats, though:
It's stored in the user registry (HKCU for instance) [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist]
The path is stored in ROT13 so for instance runme.exe would become ehazr.rkr
The registry actually stores three values in binary form: the last runtime, the run count (which starts at 6 instead of 1, for some reason), and the name of the application.
Don't know if this helps, but there you have it!
Here is a tutorial for registry handling -- C# Registry Basics
You could simply create an application setting called Properties.Settings.Default.TimesRun;
Use it like so:
private void Form1_Load( object sender, EventArgs e )
{
Properties.Settings.Default.TimesRun = timesrun++;
Properties.Settings.Default.Save();
}
No, task manager does not provide that kind of information. I wouldn't be hard to create a script that would update a tally and then execute the application and then set up the task to call the script.
I recommend using the ESENT database that is included with Windows. Software support is easily available with ESENT Managed Interface.
#Cheeso,
You don't need the private member variable with that code, one way to slim it down a bit:
using Microsoft.Win32;
public RegistryKey AppCuKey
{
get
{
return Registry.CurrentUser.OpenSubKey(AppRegyPath, true)
?? Registry.CurrentUser.CreateSubKey(AppRegyPath);
}
}
Or, if you like to update the private variable, in order to keep from calling the method (which is a pretty cheap method, anyway), you can still save yourself an if == null check.
int x = Your_Project.Properties.Settings.Default.Counter;
x++;
Your_Project.Properties.Settings.Default.Counter = x;
Your_Project.Properties.Settings.Default.Save();