I have a installer class which is taking a vale from the user at the time of installation.Now as per my requirement if the value provided is not correct i have to cancel the installation but i am not getting how to get this ..
Here is my installer.cs file..
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
// Retrieve configuration settings
string targetSite = Context.Parameters["targetsite"];
string targetVDir = Context.Parameters["targetvdir"];
string targetDirectory = Context.Parameters["targetdir"];
string value = Context.Parameters["value"];
string connectionstring = Context.Parameters["db"];
if (targetSite == null)
throw new InstallException("IIS Site Name Not Specified!");
if (targetSite.StartsWith("/LM/"))
targetSite = targetSite.Substring(4);
if (connectionstring == null)
throw new InstallException("You did not specify a database to use!");
if (value.Equals("123"))
{
RegisterScriptMaps(targetSite, targetVDir);
ConfigureDatabase(targetSite, targetVDir, connectionstring);
}
else {
Rollback(stateSaver);
}
}
Inspite of Rollback(stateSaver); my setup gets installed..
Please help me..
Just calling InstallException will do the rollback. That's the usual way and it works, so if it doesn't then something else is going on.
You should think about using another tool that lets you validate the data when it gets entered. VS custom actions always run after all the files have been installed, so basically you're installing the entire app and then rolling it back rather than using an MSI build tool that lets you validate when it's entered.
Related
I'm having an issue using the Windows credential manager in a project. I am using it to replace the username and password on the connectionString in my appsettings, and in the development and QA environments everything works fine, but in the production environment (which I don't have complete access to) it does not. The issue is its returning empty string when I load the credentials from the target.
Here is where I am loading it:
public static CredentialModel GetCredential(string target)
{
CredentialModel credentialDto = new CredentialModel();
using var credential = new Credential
{
Target = target
};
credential.Load();
credentialDto.UserName = credential.Username;
credentialDto.Password = credential.Password;
return credentialDto;
}
And this is the CredentialModel
public class CredentialModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
And where I replace the credentials in the connectionString:
StringBuilder connectionString = new(host.Configuration.GetConnectionString("RemessasConnectionString"));
var credential = CredentialService.GetCredential("Pegasus");
connectionString.Replace("$userId", credential.UserName);
connectionString.Replace("$password", credential.Password);
ConnectionString = connectionString.ToString();
For debugging's sake I added a line to the log in order to see what was being added to the connectionString, and it is replacing it with an empty string in production, but the actual values in development.
I have one idea about the reason for this, the application is running with a windows user and that user does not have access to the windows credential manager in the production server (but I think this would return an error not just empty strings).
If anyone can point me in the right direction, or has any suggestions for me to try I am all ears.
As #richard-deeming pointed out, the its because the user running the application does not have access to the credentials stored since they were stored under a different account than the one running the service. Look at his comment for more detail.
THE GOAL
I'm trying to have a class library shared between an ASP.NET Core web app and other projects/solutions and the class should be able to interact with whatever database is being used by the calling process/environment.
THE PROBLEM
I have a class library that is throwing up a weird error when used in my ASP.NET Core 3.1 web app. The class library is actually shared between the front end (the website) and the backend app that takes care of some recurring, heavy load processes. I'm using EF Core with both front and back ends and the database is on Azure, not my local machine. Yet, when the web app tries to do some work I am getting the following error:
An attempt to attach an auto-named database for file C:...\bin\Debug\netcoreapp3.1\aspnetdb.mdf failed. A database with the same name exists, or specified file cannot be opened, or it is located on UNC share.
This doesn't make sense to me at all since the DB is on Azure. Also, calling the same exact method in the library using the backend app doesn't throw up this error. The connection string is stored in appsettings.json for the website and app.config for the backend.
This is block of code that is throwing the error, but again this is only happening on the ASP.NET Core project on the SaveChanges() call:
public static void AddLogEvent(int Severity, DateTime EventTime, string EventType, string User, string Message)
{
DBEntities context = new DBEntities();
DbSet<LogEvent> dbSet = context.Set<LogEvent>();
LogEvent NewRecord = new LogEvent();
NewRecord.Id = Guid.NewGuid();
NewRecord.Severity = Severity;
NewRecord.EventTime = EventTime;
NewRecord.EventType = EventType;
NewRecord.User = User;
NewRecord.Message = Message;
dbSet.Add(NewRecord);
context.SaveChanges();
}
Within DBEntities, I am overriding the OnConfiguring() method to ensure proper connection for whatever environment is making the call as such:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
bool FoundValidConnection = false;
if (ConfigurationManager.ConnectionStrings.Count > 0)
{
foreach (ConnectionStringSettings connstr in ConfigurationManager.ConnectionStrings)
{
if (FoundValidConnection == false)
{
if (string.Compare(connstr.Name.Trim().ToUpper(), "DefaultConnection".ToUpper()) == 0)
{
optionsBuilder.UseSqlServer(connstr.ConnectionString);
FoundValidConnection = true;
break;
}
}
}
foreach (ConnectionStringSettings connstr in ConfigurationManager.ConnectionStrings)
{
if (FoundValidConnection == false)
{
if (string.Compare(connstr.Name.Trim().ToUpper(), "DBEntities".ToUpper()) == 0)
{
optionsBuilder.UseSqlServer(connstr.ConnectionString);
FoundValidConnection = true;
break;
}
}
}
if (FoundValidConnection == false)
{
//if still haven't found one of the expected connection string names, then take whatever the first one is.
optionsBuilder.UseSqlServer(ConfigurationManager.ConnectionStrings[0].ConnectionString);
FoundValidConnection = true;
}
}
else
{
//nothing to do. there are no connection strings in the ConfigurationManager
}
}
}
Lastly, when I step through debugging on the website, I can see that the only connection string located in ConfigurationManager.ConnectionStrings is 1 with a name of LocalSqlServer and a connection string set to:
data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true
Which seems to be my local testing SQLExpress instance but is not used anywhere in the web app. All references or connection strings to the local testing database in SQLExpress have been removed so I am confused as to how this is showing up and the one in appsettings.json is being ignored. I also don't understand how optionsBuilder.IsConfigured is returning FALSE on the web app. I expected that context to already be configured.
I ended up changing the last if statement to the following:
if (FoundValidConnection == false)
{
try
{
//read from appsettings.json directly instead
string connString = new ConfigurationBuilder().SetBasePath(System.IO.Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build().GetSection("ConnectionStrings").GetSection("DefaultConnection").Value;
optionsBuilder.UseSqlServer(connString);
FoundValidConnection = true;
}
catch (System.Exception ex)
{
}
}
As Ivan noted in the comments above, ConfigurationManager from System.Configuration only works with app.config, which is XML based. ASP.NET Core utilizes appsettings.json which is, obviously, JSON based. So the solution was to modify the last check to be such that if the app.config checks fail and produce nothing then we assume the code is being called by something using appsettings.json and that one line uses ConfigurationBuilder to get the app's directory, build the configuration based on that file and get the expected connection by it's section and name.
References to Microsoft.Extensions.Configuration.FileExtensions and Microsoft.Extensions.Configuration.Json were required for SetBasePath() and AddJsonFile(), respectively.
The try/catch block is needed in the event there is some unforeseen error and the appsettings/json doesn't contain the expected section/name. Although, an error would eventually get thrown up somewhere when trying to interact with the database if nothing was set.
Maybe someone knows a simple solution to my problem.
I do not know the entry of the file so it's not a static value.
It can be changed through the BizTalk gui and there we have a URI through the receiveport. But I do not believe it's accessible that easy. What I want to do is write out the full path as the filename. It works well with the messageID where the file is given a specific filepath name. But the Path-Name where the file was dropped is not working that well.
I keep getting this error :
Message:
Object reference not set to an instance of an object.
the message resource is present but the message is not found in the string/message table
-Does not say me much
Below you can see a snip from my code
internal static string UpdateMacroPathProperty(IBaseMessage baseMessage, string macroPathProperty, string macroDefsFile)
{
if (macroName == "MessageID")
{
contextPropertyValue = baseMessage.MessageID.ToString();
}
else if (macroName == "SourceFileName")
{
contextPropertyValue = Directory.GetCurrentDirectory();
}
}
This is an specific created pipeline. Has anyone encountered this problem or can point me in the right way.
I know that BizTalk has a built in function for this, BizTalk Server: List of Macros as the %SourceFileName% but I'm trying to save this as logs in a specific map structure so that it does not get processed.
It's adapter dependent; some adapters will use the FILE adapter's namespace even though they're not the file adapter, but this is the kind of logic that I've used in the past for this:
string adapterType = (string)pInMsg.Context.Read("InboundTransportType",
"http://schemas.microsoft.com/BizTalk/2003/system-properties");
string filePath = null;
if (adapterType != null)
{
if (adapterType == "FILE")
{
filePath = (string)pInMsg.Context.Read("ReceivedFileName",
"http://schemas.microsoft.com/BizTalk/2003/file-properties");
}
else if (adapterType.Contians("SFTP") && !adapterType.Contains("nsoftware"))
// nsoftware uses the FTP schema
{
filePath = (string)pInMsg.Context.Read("ReceivedFileName",
"http://schemas.microsoft.com/BizTalk/2012/Adapter/sftp-properties");
}
else if (adapterType.Contains("FTP"))
{
filePath = (string)pInMsg.Context.Read("ReceivedFileName",
"http://schemas.microsoft.com/BizTalk/2003/ftp-properties");
}
}
And then you can just fall back to the MessageID if you can't get the file path from any of these.
I need to get the user directory from within a C# windows service...
...like C:\Users\myusername\
Ideally, I'd like to have the roaming path...
...like C:\Users\myusername\AppData\Roaming\
When I used the following in a console program I got the correct user directory...
System.Environment.GetEnvironmentVariable("USERPROFILE");
...but when I use that same variable in a service, I get...
C:\WINDOWS\system32\config\systemprofile
How can I get the user folder and maybe even the roaming folder location from a service?
Thanks in advance.
I have searched for getting the profile path of user from Windows service. I have found this question, which does not include a way to do it. As I have found the solution, partly based on a comment by Xavier J on his answer, I have decided to post it here for others.
Following is a piece of code to do that. I have tested it on few systems, and it should work on different OSes ranging from Windows XP to Windows 10 1903.
//You can either provide User name or SID
public string GetUserProfilePath(string userName, string userSID = null)
{
try
{
if (userSID == null)
{
userSID = GetUserSID(userName);
}
var keyPath = #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" + userSID;
var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(keyPath);
if (key == null)
{
//handle error
return null;
}
var profilePath = key.GetValue("ProfileImagePath") as string;
return profilePath;
}
catch
{
//handle exception
return null;
}
}
public string GetUserSID(string userName)
{
try
{
NTAccount f = new NTAccount(userName);
SecurityIdentifier s = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
return s.ToString();
}
catch
{
return null;
}
}
First, you'll want to use Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
Environment.SpecialFolder.ApplicationData is for roaming profiles.
Find all SpecialFolder enumeration values here: https://msdn.microsoft.com/en-us/library/system.environment.specialfolder(v=vs.110).aspx
As others have noted, the Service will run under the account LocalSystem/LocalService/NetworkService, depending on configuration: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686005(v=vs.85).aspx
A service doesn't log on like a user, unless the service is configured to use a specific user's profile. So it's not going to point to "user" folders.
I need to add a custom activity to the default workflow template to increase assembly versions at the earliest point possible in the build process.
What I would like to achieve is to create and map the exact same workspace (that is be created further down in the workflow) inside my custom activity so that I can check out an xml file, increase the version number held within, write it back to the xml file and check the xml file back in.
I'm aware that this workspace will be created later on in the workflow but that will be too late in the build process for what I'm trying to achieve, so instead of moving any of the activities or duplicating them in a position above my custom activity (this should be ok as this workspace will be deleted and recreated again later)
I think the details I need are the BuildDirectory, WorkspaceName and SourcesDirectory. Can anyone tell me how to achieve the creation of the workspace or how obtain this data in code?
the build will be carried out on a build server, and I am using TFS 2010 and c#.
Thanks in advance
I followed the series of blog articles by Ewald Hofman as a primer and created a custom activity that does the check-out, update and check-in of a GlobalAssemblyInfo file that I parse the current version from. My task is inserted at the top of the "Update Drop Location" which is right after it does the "Get the build" portion of the workflow. I just use require the IBuildDetail and a File Mask as arguments from which you can pull out the VersionControlServer to be able to access TFS. My code is below:
protected override string Execute(CodeActivityContext context)
{
// Obtain the runtime value of the input arguments.
string assemblyInfoFileMask = context.GetValue(AssemblyInfoFileMask);
IBuildDetail buildDetail = context.GetValue(BuildDetail);
var workspace = buildDetail.BuildDefinition.Workspace;
var versionControl = buildDetail.BuildServer.TeamProjectCollection.GetService<VersionControlServer>();
Regex regex = new Regex(AttributeKey + VersionRegex);
// Iterate of the folder mappings in the workspace and find the AssemblyInfo files
// that match the mask.
foreach (var folder in workspace.Mappings)
{
string path = Path.Combine(folder.ServerItem, assemblyInfoFileMask);
context.TrackBuildMessage(string.Format("Checking for file: {0}", path));
ItemSet itemSet = versionControl.GetItems(path, RecursionType.Full);
foreach (Item item in itemSet.Items)
{
context.TrackBuildMessage(string.Format("Download {0}", item.ServerItem));
string localFile = Path.GetTempFileName();
try
{
// Download the file and try to extract the version.
item.DownloadFile(localFile);
string text = File.ReadAllText(localFile);
Match match = regex.Match(text);
if (match.Success)
{
string versionNumber = match.Value.Substring(AttributeKey.Length + 2, match.Value.Length - AttributeKey.Length - 4);
Version version = new Version(versionNumber);
Version newVersion = new Version(version.Major, version.Minor, version.Build + 1, version.Revision);
context.TrackBuildMessage(string.Format("Version found {0}", newVersion));
return newVersion.ToString();
}
}
finally
{
File.Delete(localFile);
}
}
}
return null;
}