I need to associate a file extension to a specific executable application and write its value in th register , so i see this tutorial :
tutorial
so i create a new Wpf application in which i add this class :
public class FileAssociation
{
// Associate file extension with progID, description, icon and application
public static void Associate(string extension,
string progID, string description, string icon, string application)
{
Registry.ClassesRoot.CreateSubKey(extension).SetValue("", progID);
if (progID != null && progID.Length > 0)
using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(progID))
{
if (description != null)
key.SetValue("", description);
if (icon != null)
key.CreateSubKey("DefaultIcon").SetValue("", ToShortPathName(icon));
if (application != null)
key.CreateSubKey(#"Shell\Open\Command").SetValue("",
ToShortPathName(application) + " \"%1\"");
}
}
// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Registry.ClassesRoot.OpenSubKey(extension, false) != null);
}
[DllImport("Kernel32.dll")]
private static extern uint GetShortPathName(string lpszLongPath,
[Out] StringBuilder lpszShortPath, uint cchBuffer);
// Return short path format of a file name
private static string ToShortPathName(string longName)
{
StringBuilder s = new StringBuilder(1000);
uint iSize = (uint)s.Capacity;
uint iRet = GetShortPathName(longName, s, iSize);
return s.ToString();
}
}
Then, i added the icon image to the root of the project and i put this snippet :
if (!FileAssociation.IsAssociated(".akp"))
FileAssociation.Associate(".akp", "ClassID.ProgID", "akp File", "akeo.ico", #"C:\Users\Lamloumi\Desktop\MyWork\C#\App - SuiteTool\bin\x64\Debug\App - SuiteTool.exe");
But i got a problem here in this line
Registry.ClassesRoot.CreateSubKey(extension).SetValue("", progID);
So i need to know
What is the problem ie why this code didn't work?
How can i fix it?
Thanks,
Seems that you haven't enough permissions for writing in the windows registry
Try to add to project app.manifest file with content:
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
</asmv1:assembly>
or if application have to work without requesting admin rights on each start add ability to self-elevate as shown in this example UAC self-elevation
Related
I am trying to modify 2 .exes to load DevExpress dlls from 1 location.
The .exes in the "Products" folder are use the same .dlls as the launcher does. I want to avoid having to put the same .dlls into the Products directory, and instead have the .exes read from 1 directory back(the launchers directory).
How can I achieve this?
You can handle the AppDomain.AssemblyResolve event and load the assemblies from the directory yourself using Assembly.LoadFile giving the fullpath to the assembly it is trying to resolve.
Example:
.
.
.
// elsewhere at app startup time attach the handler to the AppDomain.AssemblyResolve event
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
.
.
.
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
// this.ReadOnlyPaths is a List<string> of paths to search.
foreach (string path in this.ReadOnlyPaths)
{
// If specified assembly is located in the path, use it.
DirectoryInfo directoryInfo = new DirectoryInfo(path);
foreach (FileInfo fileInfo in directoryInfo.GetFiles())
{
string fileNameWithoutExt = fileInfo.Name.Replace(fileInfo.Extension, "");
if (assemblyName.Name.ToUpperInvariant() == fileNameWithoutExt.ToUpperInvariant())
{
return Assembly.Load(AssemblyName.GetAssemblyName(fileInfo.FullName));
}
}
}
return null;
}
You may set folder(s) path in assemblyBinding>probing::privatePath tag in app.config for the common language runtime to search when loading assemblies.
like this code
Reference MSDN
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="libs" />
</assemblyBinding>
</runtime>
</configuration>
You can create a class:
using System;
using System.Reflection;
namespace myNamespace
{
public sealed class EntryPoint
{
[STAThread]
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
var app = new App();
app.InitializeComponent();
app.Run();
}
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
{
// do whatever necessary...
}
}
}
As WPF already has a built-in Main() method, you will get a compiler error. So go to project properties > Application > "Startup object" and set it to myNamespace.EntryPoint. In your own Main() method, you have full control over everything, so you can set the AssemblyResolve handler before instantiating the App.
I am working on an app that is responsible for formatting a USB drive and prepare it for further use on an embedded system.
I am formatting the drive using the following method that I found on stack overflow (unfortunately I did not save the link. I'll post it there if I find it again)
public static bool FormatUSB(string driveLetter, string fileSystem = "FAT32", bool quickFormat = true,
int clusterSize = 4096, string label = "USB_0000", bool enableCompression = false)
{
//add logic to format Usb drive
//verify conditions for the letter format: driveLetter[0] must be letter. driveLetter[1] must be ":" and all the characters mustn't be more than 2
if (driveLetter.Length != 2 || driveLetter[1] != ':' || !char.IsLetter(driveLetter[0]))
return false;
//query and format given drive
//best option is to use ManagementObjectSearcher
var files = Directory.GetFiles(driveLetter);
var directories = Directory.GetDirectories(driveLetter);
foreach (var item in files)
{
try
{
File.Delete(item);
}
catch (UnauthorizedAccessException) { }
catch (IOException) { }
}
foreach (var item in directories)
{
try
{
Directory.Delete(item);
}
catch (UnauthorizedAccessException) { }
catch (IOException) { }
}
ManagementObjectSearcher searcher = new ManagementObjectSearcher(#"select * from Win32_Volume WHERE DriveLetter = '" + driveLetter + "'");
foreach (ManagementObject vi in searcher.Get())
{
try
{
var completed = false;
var watcher = new ManagementOperationObserver();
watcher.Completed += (sender, args) =>
{
Console.WriteLine("USB format completed " + args.Status);
completed = true;
};
watcher.Progress += (sender, args) =>
{
Console.WriteLine("USB format in progress " + args.Current);
};
vi.InvokeMethod(watcher, "Format", new object[] { fileSystem, quickFormat, clusterSize, label, enableCompression });
while (!completed) { System.Threading.Thread.Sleep(1000); }
}
catch
{
}
}
return true;
}
I also added all the capabilities that should be required (I think) in order to access a removable drive in my manifest:
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10"
IgnorableNamespaces="uap mp rescap iot">
<Identity
Name="7b9becad-6afd-4872-bcb7-7f414c098edf"
Publisher="CN=vitto"
Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="7b9becad-6afd-4872-bcb7-7f414c098edf" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>DiskMakerApp</DisplayName>
<PublisherDisplayName>vitto</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate"/>
</Resources>
<Applications>
<Application Id="App"
Executable="$targetnametoken$.exe"
EntryPoint="DiskMakerApp.App">
<uap:VisualElements
DisplayName="DiskMakerApp"
Square150x150Logo="Assets\Square150x150Logo.png"
Square44x44Logo="Assets\Square44x44Logo.png"
Description="DiskMakerApp"
BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png"/>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="broadFileSystemAccess" />
<rescap:Capability Name="appCaptureSettings" />
<Capability Name="internetClient" />
<uap:Capability Name="removableStorage" />
<iot:Capability Name="systemManagement"/>
<DeviceCapability Name="usb"/>
</Capabilities>
</Package>
And also allowed access to the file system in Window's settings page:
But I am still getting:
I am wondering if I am missing anything. Is there a way I could run the app as an administrator^ Would that solve the issue? (In any case, only admins would be able to run that app in a real life scenario)
UWP application unable to access USB drive even though permissions are set
Directory.GetFiles can't not use to access file with path in UWP platform. And you can only use Windows Storage API to access file with path (enable broadFileSystemAccess ), by the way, System.Management Namespace is not work for UWP platform, and if you want to format USB device within UWP app, please use desktop extension to process. for more please refer stefan' blog UWP with Desktop Extension
I have a console program 'A' that at a given point will run program 'B' and program 'C'. However I'm having an issue with the app.config associate with each of the program. Basically program A is just a wrapper class that calls different console application, It should not have any app.config but it should use the current running program's app config. So in theory there should be only 2 app.config one for Program B and another for program C.
So if we run Program A and program B gets executed, it should use program B's app.config to get the information and after when program C gets executed it should use Program C's app.config.
Is there a way to do this? Currently i'm doing this:
var value = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings.Settings["ProgramBKey"].Value;
It does not seem to work. I checked the debug on Assembly.GetExecutingAssembly().Location it's variable is the \bin\Debug\ProgramB.exe and 'ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location).AppSettings' has setting with Count=0 when there are key values as seen below.
sample code Program A:
static void Main(string[] args)
{
if(caseB)
B.Program.Main(args)
else if(caseC)
C.Program.Main(args)
}
sample app.config for Program B:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="ProgramBKey" value="Works" />
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
</configuration>
Edit: the following answer pertains to this question from the original post, "Is it possible to compile the app.config for B and C within the exe of the program."
You can use the "Embedded Resource" feature. Here's a small example of using an XML file that's been included as an embedded resource:
public static class Config
{
static Config()
{
var doc = new XmlDocument();
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Fully.Qualified.Name.Config.xml"))
{
if (stream == null)
{
throw new EndOfStreamException("Failed to read Fully.Qualified.Name.Config.xml from the assembly's embedded resources.");
}
using (var reader = new StreamReader(stream))
{
doc.LoadXml(reader.ReadToEnd());
}
}
XmlElement aValue = null;
XmlElement anotherValue = null;
var config = doc["config"];
if (config != null)
{
aValue = config["a-value"];
anotherValue = config["another-value"];
}
if (aValue == null || anotheValue == null)
{
throw new XmlException("Failed to parse Config.xml XmlDocument.");
}
AValueProperty = aValue.InnerText;
AnotherValueProperty = anotherValue.InnerText;
}
}
You can have multiple application using the same config file. That way when you switch applications, they can both find their own parts of the config file.
The way I usually do it is... first let each application "do its own thing", then copy the relevant sections of config file A into config file B.
It will look like this:
<configSections>
<sectionGroup>
<sectionGroup name="applicationSettings"...A>
<sectionGroup name="userSettings"...A>
<sectionGroup name="applicationSettings"...B>
<sectionGroup name="userSettings"...B>
<applicationSettings>
<A.Properties.Settings>
<B.Properties.Settings>
<userSettings>
<A.Properties.Settings>
<B.Properties.Settings>
For me, the whole thing sounds like a "design issue". Why should you want to open Programm B with the config of Programm A?
Are you the author of all those Programms? You might want to use a dll-file instead. This will save you the trouble as all code runs with the config of the Programm running.
Here how you can do it:
Make App.config as "Embedded Resource" in the properties/build action
Copy to output Directory : Do not copy
Add this code to Proram.cs Main
if (!File.Exists(Application.ExecutablePath + ".config"))
{
File.WriteAllBytes(Application.ExecutablePath + ".config", ResourceReadAllBytes("App.config"));
Process.Start(Application.ExecutablePath);
return;
}
Here are the needed functions:
public static Stream GetResourceStream(string resName)
{
var currentAssembly = Assembly.GetExecutingAssembly();
return currentAssembly.GetManifestResourceStream(currentAssembly.GetName().Name + "." + resName);
}
public static byte[] ResourceReadAllBytes(string resourceName)
{
var file = GetResourceStream(resourceName);
byte[] all;
using (var reader = new BinaryReader(file))
{
all = reader.ReadBytes((int)file.Length);
}
file.Dispose();
return all;
}
I have written a simple Installer that gets the value of property when I gave using following command:
msiexec /i file.msi /l*v output.txt IPADDRESS="192.168.2.1"
I extracted the value of IPADDRESS in C# Custom action and created two folder output and config. In config, I write the content of IPADDRESS and output is for logging. Here's my C# code:
namespace SetupCA
{
public class CustomActions
{
[CustomAction]
public static ActionResult WriteFileToDisk(Session session)
{
session.Log("Begin WriteFileToDisk");
string ipAddress = session["IPADDRESS"];
string path = session["LocalAppDataFolder"]; //With trailing slash
path = path.Replace(#"\", #"\\").ToString();
string log_path = path + #"lpa\\output\\";
string config_path = path + #"lpa\\config\\";
session.Log("Local App Data Modified Path is: " + path.ToString());
session.Log("Logging Folder Path is: " + log_path.ToString());
string temp = #"
{{
""logpoint_ip"" : ""{0}""
}}";
string config = string.Format(temp, ipAddress);
session.Log("Config Generated from property is: " + config);
System.IO.Directory.CreateDirectory(config_path);
try
{
System.IO.File.Delete(path + "lpa.config");
}
catch (Exception e)
{
session.Log(e.ToString());
}
System.IO.File.WriteAllText(config_path + "lpa.config", config);
session.Log("Confile file is written");
System.IO.Directory.CreateDirectory(log_path);
session.Log("Logging Folder is Created");
return ActionResult.Success;
}
}
}
Now I have created a Visual C++ application that checks if the program has been registered during startup or not. If not, it adds the exe file in Registry and enters the Infinite loop. If I run the installed exe, it appears in command window. What I want is to run the exe in background and user could view the exe inside Process in Task Manager. I don't want to disturb user by showing a blank terminal window that seems to do nothing. Can this be done in Wix or should I change my code?
I have attached my C++ code and Wix File.
CODE
#include <iostream>
#include <Windows.h>
#include <ShlObj.h>
#include <log4cplus/logger.h>
#include <log4cplus/fileappender.h>
#include <log4cplus/layout.h>
#include <log4cplus/ndc.h>
#include <log4cplus/helpers/loglog.h>
#include <log4cplus/loggingmacros.h>
#include <boost\lexical_cast.hpp>
#include <boost\algorithm\string\replace.hpp>
using namespace log4cplus;
Logger root;
std::string GetLocalAppDataPath();
void LoggingInit();
void LoggingInit()
{
log4cplus::initialize ();
helpers::LogLog::getLogLog()->setInternalDebugging(false);
std::string local_path = GetLocalAppDataPath();
local_path = local_path + "\\lpa\\output\\";
SharedAppenderPtr append_1(new RollingFileAppender(LOG4CPLUS_TEXT( local_path + "outputgen.log"), 10*1024*1024, 5));
append_1->setName(LOG4CPLUS_TEXT("LogpointAgentLog"));
PatternLayout *p = new PatternLayout(LOG4CPLUS_TEXT("[%D] <%-5p> [%F : %L] %m%n"));
append_1->setLayout(std::auto_ptr<Layout>(p));
Logger::getRoot().addAppender(append_1);
root = Logger::getRoot();
}
std::string GetLocalAppDataPath()
{
HANDLE hfile;
TCHAR szPath[MAX_PATH];
if(SUCCEEDED(SHGetFolderPath(NULL,CSIDL_LOCAL_APPDATA,NULL,0, szPath)))
{
std::string path = boost::lexical_cast<std::string>(szPath);
boost::replace_all(path, "\\", "\\\\");
return path;
}
}
BOOL IsMyProgramRegisteredForStartup(PCWSTR pszAppName)
{
HKEY hKey = NULL;
LONG lResult = 0;
BOOL fSuccess = TRUE;
DWORD dwRegType = REG_SZ;
wchar_t szPathToExe[MAX_PATH] = {};
DWORD dwSize = sizeof(szPathToExe);
lResult = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_READ, &hKey);
fSuccess = (lResult == 0);
if (fSuccess)
{
lResult = RegGetValueW(hKey, NULL, pszAppName, RRF_RT_REG_SZ, &dwRegType, szPathToExe, &dwSize);
fSuccess = (lResult == 0);
}
if (fSuccess)
{
fSuccess = (wcslen(szPathToExe) > 0) ? TRUE : FALSE;
}
if (hKey != NULL)
{
RegCloseKey(hKey);
hKey = NULL;
}
return fSuccess;
}
BOOL RegisterMyProgramForStartup(PCWSTR pszAppName, PCWSTR pathToExe)
{
HKEY hKey = NULL;
LONG lResult = 0;
BOOL fSuccess = TRUE;
DWORD dwSize;
const size_t count = MAX_PATH*2;
wchar_t szValue[count] = {};
wcscpy_s(szValue, count, L"\"");
wcscat_s(szValue, count, pathToExe);
wcscat_s(szValue, count, L"\" ");
lResult = RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, NULL, 0, (KEY_WRITE | KEY_READ), NULL, &hKey, NULL);
fSuccess = (lResult == 0);
if (fSuccess)
{
dwSize = (wcslen(szValue)+1)*2;
lResult = RegSetValueExW(hKey, pszAppName, 0, REG_SZ, (BYTE*)szValue, dwSize);
fSuccess = (lResult == 0);
}
if (hKey != NULL)
{
RegCloseKey(hKey);
hKey = NULL;
}
return fSuccess;
}
int main()
{
//std::string loc = GetLocalAppDataPath(); //Without trailing slashes
LoggingInit();
if(IsMyProgramRegisteredForStartup(L"My_Program"))
{
//do nothing
}
else
{
LOG4CPLUS_INFO(root, "Starting Starup App");
wchar_t szPathToExe[MAX_PATH];
GetModuleFileNameW(NULL, szPathToExe, MAX_PATH);
RegisterMyProgramForStartup(L"My_Program", szPathToExe);
LOG4CPLUS_INFO(root, "Ending Starup App");
}
LOG4CPLUS_INFO(root, "BEFORE INFINITE LOOP #######################");
while(1)
{
LOG4CPLUS_INFO(root, "INSIDE THE WHILE LOOOOOOPPPP");
Sleep(5000);
}
return 0;
}
WIX FILE
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="InstallerForStartupCPP" Language="1033" Version="1.0.0.0" Manufacturer="LPAA" UpgradeCode="70510e56-b6ab-4e6f-beb6-40bb2e30c568">
<Package InstallerVersion="200" Compressed="no" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature" Title="InstallerForStartupCPP" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="Startup CPP" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent">
<File
Id="STARTUPCPPINSTALLER"
Name="StartupCPPInstaller.exe"
DiskId="1"
Source="$(var.StartupCPPInstaller.TargetPath)"
Vital="yes"
KeyPath="yes" />
</Component>
<Component Id="log4cplus">
<File Source ="G:\SarVaGYa\myworkspace\LatestLpa\lpa\lpa_c\ext_library\log4cplus\bin\Release\log4cplus.dll" />
</Component>
</ComponentGroup>
<Binary Id="SetupCA" SourceFile="..\SetupCA\bin\Release\SetupCA.CA.dll"/>
<CustomAction Id="WRITEFILETODISK" Execute="immediate" BinaryKey="SetupCA" DllEntry="WriteFileToDisk" />
<InstallExecuteSequence>
<Custom Action="WRITEFILETODISK" Sequence="2"></Custom>
</InstallExecuteSequence>
</Fragment>
</Wix>
How am I supposed to install the MSI file and run the program in background.? Please help.
PS: I have tried making a service. I do not get GetLocalAppDataPath true value if I run the program as service.
The straightforward way would be to change C++ application subsystem type to windows in project settings: Linker->System->SubSystem and replace main function with WinMain - it will not create any windows itself, you can ignore its parameters and do what you currently do in main.
All "RegisterMyProgramForStartup" is doing is writing a registry value. This is basic Windows Installer 101 functionality exposed by the RegistryValue element. All of this custom action code is an antipattern.
To recreate my production environment I created the following folder structure:
c:\TEST\tested.dll
c:\TEST\tested\tools.dll
The tested.dll is compiled using the following App.config file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="tested"/>
</assemblyBinding>
</runtime>
</configuration>
As far as I know the application should look for it's tools file in the subfolder. When I try to start the station I still get the error that the file was not found.
To give some context here is an example tested.dll source:
namespace ConsoleApplication1
{
public static class Testable
{
public static tools.IToolAble usefultool = null;
public static void initialisation()
{
if (usefultool == null) usefultool = new UsefulTest()
}
}
public class UsefulTest : tools.IToolAble
{
}
}
and an example tools.dll source:
namespace tools
{
public interface IToolAble
{
}
}
The code that crashes is my testcode that works like this:
private CustomMock controller = new CustomMock();
public void TestFixtureSetUp()
{
controller.LoadFrom(#"c:\TEST\tested.dll");
//The next line crashes because tools assembly is needet but not found
controller.InvokeInitialisation();
}
What am I missing?
Is the App.config correct?
EDIT:
The Answer below is correct, the path is only known once the correct dll can be chosen. So the other team has to add a new ResolveEventHandler before loading. Here is a simplified version of that:
internal void AddResolveEventHandler(string assemblyname, string assemblylocation)
{
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(
(sender, args) =>
{
Assembly ret = null;
if (
new AssemblyName(args.Name).Name == assemblyname &&
File.Exists(assemblylocation))
{
ret = Assembly.LoadFrom(assemblylocation);
}
return ret;
}
);
}
the tested.dll is compiled using the following App.config file
It needs to be an yourapp.exe.config file, not a .config file for the DLL. The CLR only ever looks for a .config file associated with the main process.
And watch out for app.vshost.exe.config, required when you debug with the hosting process enabled.
And watch out when using unit test runners, another .exe file
Do consider if this is actually worth the trouble. Your user won't care where the DLL is located.