CommonApplicationData folder read-only after using MSIX Packaging Tool - c#

I have written a .Net Windows Forms application that uses the common application data folder to store logfiles and user accounts. The application is distributed using an install shield project and runs perfect on all different Windows versions.
Some parts of the code from different files is shown below
// Defining the path to use (in ProductInfo class)
public static string CommonApplicationDataPath
{
get
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
path = StringHelper.EnsureEndsWithSlash(path);
path += Vendor + #"\" + ProductName + #"\";
return path;
}
}
// Configuring the logger and user manager instances at startup
Logger.Configure(string.Empty, ProductInfo.Password, ProductInfo.CommonApplicationDataPath);
UserManager.Configure(User.Empty, ProductInfo.Password, ProductInfo.CommonApplicationDataPath,
ProductInfo.UserLimitCount);
// Example method for saving the users to file (in UserManager class)
public bool SaveUsers(AppUsers appUsers)
{
AppUsersSerializer serializer = new AppUsersSerializer(_password, _fileName);
if (serializer.Serialize(appUsers) == true)
{
return true;
}
else
{
Logger.Instance.Log(Logs.ErrorB.UserSave, _fileName);
return false;
}
}
I would now like to publish the application via Windows Store and have used the MSIX Packaging Tool. To sign the package I have created a self signed certificate and added it to the Trusted Root Certificate Authorities. The .msix package is install on the same PC as my old desktop version of the app.
The problem I have is that the application is not able to write to the files located in the CommonApplicationData folder. The application can read and load the data, but not update and write the changes to the files. Thus, the path to the files is correct, but some write permission seems to be missing. I have tried different capabilities on the package and even ticked all, but without any effect.
I have also browsed to the C:\Program Files\WindowsApps\<my app package>\ folder and checked the structure of the application and located the files. They are there, but only readable for the app. Removing the files will not create new ones when they should be added as done in the old desktop Windows Forms version.
The application is quite big and contains lots of functionality which runs great in the Windows Store app context. The only missing piece is the above mentioned issues with the file writing.
Any advice would be really appreciated.

After some continued searching on different websites I came across a viable solution for my issue.
The Microsoft MSDN blog described how to use the different folders in an appropriate way.
http://blogs.msdn.microsoft.com/appconsult/2017/03/06/handling-data-in-a-converted-desktop-app-with-the-desktop-bridge/
The proposed solution is to change:
string path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
to:
string path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
It will place the files in the user's local directory, meaning the data will be available only for the current user. Sharing the same log file and user accounts between different users of the application, will thus not be possible but that is ok for now.
You may also need to make sure that the folder exists:
C:\Users\<user>\AppData\Local\<vendor>\<product> because it might not always be created during installation of your application. It depends if it has user specific settings or not.

CommonApplicationData folder read-only after using MSIX Packaging Tool
If you have converted your app to store app, we could regard it as UWP app, In general, we store the user info with LocalSettings class that could keep the data during the app updating.
var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
// Create a simple setting.
localSettings.Values["exampleSetting"] = "Hello Windows";
// Read data from a simple setting.
Object value = localSettings.Values["exampleSetting"];
if (value == null)
{
// No data.
}
else
{
// Access data in value.
}
// Delete a simple setting.
localSettings.Values.Remove("exampleSetting");
For more detail, please refer Store and retrieve settings and other app data

Related

OfficeConverter issue when deploying to Azure App Service

I have a web API which simply
clone a .docx file
convert that cloned .docx to a .pdf format
using DocumentFormat.OpenXml.Packaging;
[HttpPost("clone")]
public IActionResult CloneBillFromTemplate()
{
var templateFilePath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "Bill", "PaymentTempl.docx");
var clonedFilePath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "Bill", "ClonedBill.docx");
var pdfFilePath = System.IO.Path.Combine(Directory.GetCurrentDirectory(), "Bill", "FinalBill.pdf");
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(templateFilePath, true))
{
var clonedDoc = wordDoc.Clone(clonedFilePath);
System.Threading.Thread.Sleep(500);
clonedDoc.Save();
clonedDoc.Close();
}
using (var converter = new OfficeConverter.Converter())
{
converter.Convert(clonedFilePath, pdfFilePath);
}
return Ok();
}
Everything works fine when debugging (for sure :3) and also on IIS
But when I deploy to Azure App service, I got this type of error (stack trace + exception message).
Could not read registry to check Word version
Could not find registry key Word.Application\CurVer
at OfficeConverter.Word..ctor()
at OfficeConverter.Converter.get_Word()
at OfficeConverter.Converter.Convert(String inputFile, String outputFile, Stream logStream) at ....
Could you guys help me on this? Thanks all!!!
**feel free to ask for more information you need to detect this issue
Update
Looks like this is the issue with the pdf converter pacakge I'm using, not the OpenXML
It seems your application is relying on the Windows registry in a way that is not supported. If you are running on a Linux App Service, that would be the first thing to swap out, though my guess is that you are already running on Windows.
Apps have read-only access to much (though not all) of the registry of the virtual machine they are running on. In practice, this means registry keys that allow read-only access to the local Users group are accessible by apps. One area of the registry that is currently not supported for either read or write access is the HKEY_CURRENT_USER hive.
Write-access to the registry is blocked, including access to any per-user registry keys.
https://learn.microsoft.com/en-us/azure/app-service/operating-system-functionality#registry-access
If you can't refactor your code to not rely on such dependencies, I would suggest you put your application inside a Windows docker container. If you can run that locally, it should run on App Service as well.

how to create sqlite database in windows store app located at a specific folder

Need help, I can't seem to access any other .sqlite database if its not located in the apps localfolder. Every tutorial I look at they always use
Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "Example.sqlite");
I tried this:
const string testing = #"C:\Users\***\AppData\Local\Packages\*************\LocalState";
this.DBPath = Path.Combine(testing, "Example.sqlite");
using (var db = new SQLite.SQLiteConnection(this.DBPath))
{
db.CreateTable<Customer>();
}
and it worked. but when I change it to:
const string testing = #"C:\Databases";
It can't open the database even if I copied the database from the local folder of the app.
Any suggestions ? I'm still trying to learn.
You can't access the C: drive for windows store apps. It's part of the store's sandbox. Each app is limited to which files and folders can be viewed. If you have a local database file you need to access, define the file as content in your app and access it using the path "ms-appx:///..."
use file picker to select which folder you want to save the db file.
refer this article:
http://msdn.microsoft.com/en-us/library/windows/apps/hh967755.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

Denied acces to a file

I have a code which is similar this:
string file;
using (StreamReader r = new StreamReader("xml.xml"))
{
file = r.ReadToEnd();
}
XElement xml = XElement.Parse(file);
using (XmlWriter w = XmlWriter.Create("xml.xml")) //The point of problem!
{
w.WriteStartDocument();
...;
w.WriteEndDocument();
}
When I try run it like a console application is everything all right. But problems start when I want to use it in an ASP.NET application. At the using line it throws UnauthorizedAccessException exception with a description "access to the path is denied". Why?
You need to check which account your application Pool is using to access your server files/folders, for example, make one code to copy one file to application folder, check all security info, copy and paste on this problem folder, normally use this account "IIS_IURRS" give full control to test only...
If IIS/the web server is configured correctly, an account with a very limited set of permissions is used. As your path points to the application directory, it is very likely that the application pool account is not allowed to write to this location.
If you run the code in a console application, your user's permissions are applied and it is more than likely that you are allowed to write to the output folder of the project as Visual Studio writes the build output there under your account.
I would not recommend to change the application pool account or the permissions of the application folder in the file system - it is a very sensible limitation that limits the amount of trouble an attacker can possibly make.
Therefore I'd recommend to either move the file to a folder that the account can write to without changing permissions or define a special one outside of the application folder hierarchy that the account is given permissions to.
Also keep in mind that multiple users might access the file at the same time, so a database might be a better choice to store the data.

How to get the location of a file located within a Windows application folder

What I am trying to do is read data from a CSV file located within a Windows application folder named "Attachments". In a web application you can get the path of the folder using
Server.MapPath(#"Attachments/sample.csv");
What is the equivalent call from a Windows application?
Below is my code.
string[] str = File.ReadAllLines(#"Attachment/sample.csv");
// create new datatable
DataTable dt = new DataTable();
// get the column header means first line
string[] temp = str[0].Split(';');
// creates columns of gridview as per the header name
foreach (string t in temp)
{
dt.Columns.Add(t, typeof(string));
}
Is the path relative to the executable? If so you can use Application.StartupPath to determine where the program was started, and then combine that with the relative file path to get the full path:
var fullPath = Path.Combine(Application.StartupPath, #"Attachment\sample.csv");
If your app is running as a service or uses ClickOnce deployment this won't work, though.
The Windows Application Folder could be identified by this enum
Environment.SpecialFolder.ProgramFiles
MSDN refs
To get a string containing the actual path and the full file name you write
string pathToFile = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
string fullFileName = Path.Combine(pathToFile, #"Attachment\sample.csv");
As noted by Cole Johnson in its comment, there is a problem if your hosting operating system is 64bit. In that case there are two application folders. One for 64bit apps and one for 32bit apps.
Using NET4, you could discover the current operating system bitness with the property
Environment.Is64BitOperatingSystem
and, if false, use a different enum
Environment.SpecialFolder.ProgramFilesX86
But after all this, I think you should change something in the architecture of your program.
In Web applications, usually you do not use folders outside the web-root.
But WinForms application has no such limitation and then you may install the CSV files in a different folder (MyDocuments comes to mind) and control the actual location on your hard disk via an option in the configuration file
REMEMBER: The Application Folder requires particular permission to write in (If you save your attachments there this is another reason to choose a different location than the Application Folder)
You can do this with So something like
Path.Combine(Application.StartupPath, #"Attachment\sample.csv");
Steve beat me to it but yeah, check intellisense for whatever folder you're looking for under :
string path = Environment.GetFolderPath(Environment.SpecialFolder.
properties
You'll find all kinds of system paths there.

Online ClickOnce Deployment Application and place an icon on the desktop/start menu

I'm working on a online only winform application which I deploy using ClickOnce feature it uploads through FTP to the server and the user executes it online through http.
As you may already know, the Online only feature doesn't place any icons on the desktop, so everytime it runs the user got to run the setup.exe file to do it.
My question is, if there is anyway I could actually create an icon that may point to the setup file or any workaround to make sure the user got an accesible and easy way to run the app without having to look for the setup file everytime?
Users may not know a lot about computers so it can be a hard task to navigate all the way to the downloaded file everytime, and I want to make it easier for them.
I know that if I do an offline/online app it will solve the problem, but I would like it to be online only.
Any ideas?
you can create desktop shortcut manually on the first app run, and point it to either to your app's url, or path to downloaded file (I guess, url will be safer in case user deletes the file). Code can look something like this (need adjusting to your URL):
void CheckForShortcut()
{
ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
if (ad.IsFirstRun)
{
Assembly code = Assembly.GetExecutingAssembly();
string company = string.Empty;
string description = string.Empty;
if (Attribute.IsDefined(code, typeof(AssemblyCompanyAttribute)))
{
AssemblyCompanyAttribute ascompany = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(code,
typeof(AssemblyCompanyAttribute));
company = ascompany.Company;
}
if (Attribute.IsDefined(code, typeof(AssemblyDescriptionAttribute)))
{
AssemblyDescriptionAttribute asdescription = (AssemblyDescriptionAttribute)Attribute.GetCustomAttribute(code,
typeof(AssemblyDescriptionAttribute));
description = asdescription.Description;
}
if (company != string.Empty && description != string.Empty)
{
string desktopPath = string.Empty;
desktopPath = string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"\\", description, ".appref-ms");
string shortcutName = string.Empty;
shortcutName = string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.Programs),
"\\", company, "\\", description, ".appref-ms");
System.IO.File.Copy(shortcutName, desktopPath, true);
}
}
}
credits to http://geekswithblogs.net/murraybgordon/archive/2006/10/04/93203.aspx
What is your reason for wanting an online only ClickOnce app? I always recommend offline unless your app is really an edge case.
There's very little difference between online and offline. All the same files are downloaded to the same location on the client. Offline apps add an entry to the 'Add/Remove Programs', a start menu shortcut, and an optional desktop shortcut (if you are targeting .NET 3.5+). The ability to uninstall through Add/Remove Programs is key. It makes supporting your application much easier when users have install problems.
Also, you mention users running the setup.exe every time. This is unnecessary. The setup.exe will contain your bootstrapped pre-requisites and then launch the app when it finishes. If the user has run the setup.exe once, they only need to click the link to the .application file. That will definitely speed up the app's start time. Also, in many cases the user has to have admin privileges to run the setup.exe; clicking the .application doesn't (assuming someone with admin privileges has already run the setup.exe).
In conclusion, there really isn't an answer here :). But...
Make absolutely sure your reasoning is sound for not doing an offline install instead.
After running the setup.exe once, direct users to click on the .application url (or the desktop shortcut if you switch to offline) instead of the setup.exe.
As far as I know, there is no reliable way for running online only ClickOnce application than creating shortcut to that setup.exe.

Categories

Resources