Composite C1 - 'URL Aliases', redirecting URL's with Querystring - c1-cms

I have a Composite C1 CMS site.
To maintain SEO juice, I need to redirect some old - mainly blog URLs - like this: http://www.mydomain.com/en/news/news.php?b=68
to
http://mydomain.com/en/Blog/2013/04/30/Friendly-Article-Name
and
http://www.mydomain.com/en/news/news.php?b=69
to
http://mydomain.com/en/Blog/2013/04/30/Another-Friendly-Article-Name
There are about 100 links to redirect.
The 'URL Aliases' module seems to work well, until you add a querystring (?b=68 above) - then it stops working.
How can I redirect several identical URL's, each with a different querystring?

That is definitely a bug in the Url Aliases package.
The quickest way around this would likely be to roll your own http module, at least until a fix is published.
You can snag the source from the package's repo on GitHub and tweak it to fix the issue, making sure that you unregister the bundled http module from web.config and register your own instead.
The current http module source is here: https://github.com/CPHCloud/c1packages-urlaliases/blob/v1.0.2/CphCloud.Packages.UrlAlias/UrlAliasHttpModule.cs
Change the value of incomingUrlPath to use PathAndQueryinstead of AbsolutePath, like this:
...
static void httpApplication_BeginRequest(object sender, EventArgs e)
{
var httpApplication = (HttpApplication)sender;
var incomingUrlPath = HttpUtility.UrlDecode(httpApplication
.Context.Request.Url.PathAndQuery.TrimEnd(new[] { '/' }));
....
In your web.config file you should unregister Url ALiases' handler
<!--add name="UrlAlias" type="CphCloud.Packages.UrlAlias.UrlAliasHttpModule,
CphCloud.Packages.UrlAlias,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /-->
and register your own
<add name="CustomUrlAlias" type="CphCloud.Packages.UrlAlias.UrlAliasHttpModule,
YourAssemblyName,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
Full disclosure: I'm the author of the URL Aliases package.

Related

Spring static content(css) not served properly (spring boot)

I have a spring boot app and a controller that server static webpage(React build):
#Controller
#RequestMapping("/test")
public class HomeController {
#GetMapping("/")
public String index() {
return "index.html";
}
...
index.html is located at: ../resources/static/index.html
also in application.yml:
spring:
mvc:
static-path-pattern: /test/**
I am having two problems(problem 2 is the main issue):
I must call the following url with the trailing '/' at the end: http://localhost:8100/test/ I would like for http://localhost:8100/test to also map me to the view(index.html).
during the load of the page I am getting the following error:
the problem as you can see is that the url called is:
http://localhost:8100/static/css/main.6c417d20.chunk.css
and not
http://localhost:8100/test/static/css/main.6c417d20.chunk.css
(please note that the reason for the 'static' in the url is that there is a folder named: static below the resources/static folder so there is no issue with the 'static' in the url)
is it a server side problem or is it something I should fix in the react?
I searched for an answer but didn't find anything helpful.
any help would be highly appreciated,
Tnx
So the answer to my question lies in the following links:
how to build react to a non root path(homepage):
build react non root path
registering zuul client to the following path(that contains all resources):
Zuul configuration with resources
so I am leaving this here in case someone has the same issue(the answer in the second link is for vue.js and webpack,the first link explains how to change root address in react).
Answer 1 : #RequestMapping has a String[] value parameter, so can specify multiple values like this:
#RequestMapping(value={"", "/", "welcome"})
Answer 2 : You are expecting test in URL which is controller mapping not the project context path so it should not come in static resources urls.
see this answer for more clarity adding css and js in spring boot.

Visualforce: Can't access archived static resource

I'm trying to access some static resource in my visualforce page. I have uploaded a zip file named pqGrid. It's structure is like so:
- pqgrid.min.js
- pggrid.min.css
- images/
--image1.png
--image2.png
Following the directions in the sf docs on [referencing a static resource1, I tried the following:
<apex:includeScript value="{!URLFOR($Resource.pqGrid, 'pqgrid.min.js')}"/>
<apex:stylesheet value="{!URLFOR($Resource.pqGrid, 'pqgrid.min.css')}"/>
Onloading the page, I get a 404:
GET
https://c.cs10.visual.force.com/resource/7975978979000/pqGrid/pqgrid.min.js
404 (Not Found)
GET https://c.cs10.visual.force.com/resource/7975978979000/pqGrid/pqgrid.min.css
I've even tried just referencing an image:
<apex:image url="{!URLFOR($Resource.pqGrid, 'images/cross.png')}"/>
and same 404:
GET
https://c.cs10.visual.force.com/resource/7975978979000/pqGrid/images/cross.png
404 (Not Found)
I've made sure that the resource cache control is public
What am I doing incorrectly?
EDIT
Tried putting all the files into another directory, so it is now pqGrid/stash/[*files]
When I get the 404 not found:
GET
https://c.cs10.visual.force.com/resource/1430434594000/pqGrid/stash/pqgrid.min.css
I tried playing with this url. Trying to navigate to just /stash/ throws a ERR_INVALID_RESPONSE error. Navigating to just /pqGrid/ let's me download the stuff. Trying to go directly to the file, /pqGrid/stash/file.js I just get a blank page.
So the zip is definitely on salesforce, but something is up with accessing it.
Thanks all!
This happens when we specify incorrect paths for our files would suggest to try below
<apex:includeScript value="{!URLFOR($Resource.pqGrid, 'pqgrid/pqgrid.min.js')}"/>
<apex:stylesheet value="{!URLFOR($Resource.pqGrid, 'pqgrid/pqgrid.min.css')}"/>
Sometimes forgetting slash also introduces issues
<apex:includeScript value="{!URLFOR($Resource.pqGrid, '/pqgrid.min.js')}"/>

How do you escape a . (full stop) or / so it doesn't change URL's meaning?

I have a Web API 2.0 service which defines a particular route:
/api/someEntityGroup/{entityName}
I'm calling this enpoint using Angular $resource service.
The problem is when user wants to provide an entity name with characters that have a specific meaning in URL:
404 Not found - . (full stop), /, +
400 Bad request - ?, :, &, %, *, <, >
And these are the ones I've encountered. There may be others that may be problematic as well and I'm not even aware of them (yet).
If I use window.escape() function these still don't work, but I mainly get 404 back (the only exception being * which still returns 400 Bad request).
My code
Angular resource creation:
.factory("entityResource", ["$resource", function() {
return $resource("/api/entities/:id", null, {
search: {
method: "GET",
url: "/api/entities/:name",
isArray: true
}
});
}]);
How I call it in my code:
entityResource.search({ query: scope.name }, function(data) {
...
});
My Api controller action:
[RoutePrefix("/api/entities")]
public class EntitiesController: ApiController
{
[Route("{searchQuery}")]
public IEnumerable<Interest> Get(string searchQuery)
{
return this.interestService.Search(searchQuery);
}
...
}
I can shed some light on your 404 Not found issue when using ., /, + characters.
The issue isn't with Angular but rather with Web API and the way it resolves routes. Urls that Web API interprets as being managed resources (e.g. static content, pages etc.) it will try to resolve independently.
Set the following in your web.config to disable this behavior and force WebAPI to run all requests through your modules:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
Just a warning - If your Web API is hosted together with something like MVC or a static website, the above is not recommended as it will force all managed resources (pages, MVC routes, content[css,js,images]) through your API modules and there will be a performance impact. However, if all the API is doing is serving resource routes I would recommend enabling the above.

Azure Diagnostics: Access to the path '(GUID)-mswapd-lock' is denied?

Code and configuration:
I've enabled Diagnostics per the official tutorial at https://www.windowsazure.com/en-us/develop/net/common-tasks/diagnostics/. My diagnostic initializer is invoked from Global.asax (no WebRole.cs for this WCF ported to Azure WebRole) and its quite simple like:
public bool Initialize()
{
DiagnosticMonitorConfiguration config = DiagnosticMonitor.GetDefaultInitialConfiguration();
config.WindowsEventLog.DataSources.Add("Application!*");
config.WindowsEventLog.ScheduledTransferPeriod = System.TimeSpan.FromMinutes(1.0);
DiagnosticMonitor.Start("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString", config);
return true;
}
Cloud and Local strings same:
I'm using the SAME cloud based diagnostic connection string for local and cloud configurations.
<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="MyApp.API.Azure1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="2" osVersion="*" schemaVersion="2012-05.1.7">
<Role name="MyApp.API">
<Instances count="1" />
<ConfigurationSettings>
...
<Setting name="Microsoft.WindowsAzure.Plugins.Caching.ConfigStoreConnectionString" value="DefaultEndpointsProtocol=https;AccountName=myapi;AccountKey=MyVeryLongStringHereWhichIsActuallyAKeyForAPlaceInTheCloudWhereUnicornsDanceUnderDoubleRainbows" />
</ConfigurationSettings>
<Certificates>
<Certificate name="Microsoft.WindowsAzure.Plugins.RemoteAccess.PasswordEncryption" thumbprint="ThumbPrintStringsAreBiggerThanPinkiePrintString" thumbprintAlgorithm="sha1" />
</Certificates>
</Role>
</ServiceConfiguration>
Error:
When I run the above within Azure Emulator (local compute) I do not get the error (despite the cloud connection string for diagnostics). When I run the webrole on Azure (with same diagnostic sting and of course, code), I get the following error:
[UnauthorizedAccessException: Access to the path '05d5e525-e1bc-4a37-8bfb-010bb2941301-mswapd-lock' is denied.]
System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) +12895415
System.Threading.MutexTryCodeHelper.MutexTryCode(Object userData) +229
System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData) +0
System.Threading.Mutex..ctor(Boolean initiallyOwned, String name, Boolean& createdNew, MutexSecurity mutexSecurity) +629
System.Threading.Mutex..ctor(Boolean initiallyOwned, String name, Boolean& createdNew) +18
Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.StartDiagnosticsMonitorProcess(DiagnosticMonitorStartupInfo info) +171
Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.ReconfigureMonitoringProcess(ConfigRequest req) +209
Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.UpdateState(DiagnosticMonitorStartupInfo startupInfo) +207
Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.StartWithExplicitConfiguration(DiagnosticMonitorStartupInfo startupInfo, DiagnosticMonitorConfiguration initialConfiguration) +643
Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.Start(CloudStorageAccount storageAccount, DiagnosticMonitorConfiguration initialConfiguration) +47
Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitor.Start(String diagnosticsStorageAccountConfigurationSettingName, DiagnosticMonitorConfiguration initialConfiguration) +108
myApp.api.Diag.Diagnostics.Initialize() in c:\Work\MyApp.API\source\Diag\Diagnostics.cs:42
Global.Application_Start(Object sender, EventArgs e) in c:\Work\MyApp.API\source\Global.asax.cs:30
Attempts: None worked
Disabled all Azure monitoring and logging (from portal) for this storage account in case Azure's own monitoring/logging mechanisms were locking it down
Replaced UseDevelopmentStorage=true with real cloud connection string for diagnostics even for local configuration (local compute/Azure emulator).
Simplified diagnostic initializer to bare minimum (seen above). However, DiagnosticMonitor.Start(...) always fails.
Created another diagnostic connection string in .cscfg file (with reference in .csdef too) so that if the original Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString is also used by Azure infrastructure, I have another string for it. No help, same error.
I've burnt many hours trying to debug this but I always get this error on Azure.
Question:
Can someone help me get rid of this error? I can try a few ideas you may have. I'm disappointed by the MS tutorial but disappointment doesn't help.
Exactly the same symptoms here (but in an ASP.NET MVC application).
Basically you shouldn't be using the DiagnosticMonitor.Start() any more.
The below worked for me (Azure SDK 1.8, October 2012)
I simplified the init code from this article:
http://convective.wordpress.com/2010/12/01/configuration-changes-to-windows-azure-diagnostics-in-azure-sdk-v1-3/
private void ConfigureDiagnostics()
{
var wadConnectionString ="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString";
var cloudStorageAccount = CloudStorageAccount.Parse(RoleEnvironment.GetConfigurationSettingValue(wadConnectionString));
var roleInstanceDiagnosticManager =
cloudStorageAccount.CreateRoleInstanceDiagnosticManager(
RoleEnvironment.DeploymentId,
RoleEnvironment.CurrentRoleInstance.Role.Name,
RoleEnvironment.CurrentRoleInstance.Id);
var diagnosticMonitorConfiguration = roleInstanceDiagnosticManager.GetCurrentConfiguration();
diagnosticMonitorConfiguration.Directories.ScheduledTransferPeriod = TimeSpan.FromMinutes(1d);
diagnosticMonitorConfiguration.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(1d);
diagnosticMonitorConfiguration.Logs.ScheduledTransferLogLevelFilter = LogLevel.Verbose;
roleInstanceDiagnosticManager.SetCurrentConfiguration(diagnosticMonitorConfiguration);
}
I'm calling it from the Application_Start() in Global.asax.cs and it works fine now. Both locally and in the cloud.
You also need this in your web.config:
<system.diagnostics>
<trace>
<listeners>
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="AzureDiagnostics">
<filter type="" />
</add>
</listeners>
</trace>
</system.diagnostics>
and this in your ServiceDefinition.csdef 's WebRole section:
<Imports>
<Import moduleName="Diagnostics" />
</Imports>
These are added by the wizard by default, but still worth checking when migrating existing code to Azure.
A note to log4net users:
Specialized appenders are not really necessary, you can use the standard log4net.Appender.TraceAppender which comes with log4net - just configure it in your web.config and init log4net as usual it in your Application_Start() or prior to the 1st use.
Removing from trace listeners element this line fixed the problem for me.
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics" />
Know I'm thinking how to update already existing application configuration and not create one during the application start.

Can I force the installer project to use the .config file from the built solution instead of the original one?

I am using the solution to this question in order to apply configuration changes to App.config in a Winforms project. I also have an installer project for the project that creates an installable *.msi file. The problem is, the config file bundled in the installers is the original, un-transformed config file. So we're not getting the production connection strings in the production installer even though the config file for the built winforms project has all the correct transformations applied.
Is there any way to force the installer project to use the output of project build?
First of all: it is impossible to make the Setup Project point to another app.config file by using the Primary output option. So my solution is going to be a work around. I hope you find it useful in your situation.
Overview:
The basic idea is:
Remove the forced app.config from the Setup Project;
Add a file pointing to the app.config, manually;
Use MSBuild to get into the vdproj file, and change it to match the real output of the transformed app.config.
Some drawbacks are:
The setup project only gets updated, if the project it deploys build. ahhh... not a real drawback!
You need MSBuild 4.0... this can also be worked around!
Need a custom Task, called FileUpdate... it is open source and has installer.
Lets Work:
1) Go to your Setup Project, and select the Primary Output object, right click and go to Properties. There you will find the Exclude Filter... add a filter for *.config, so it will remove the hard-coded app.config.
2) Right click your Setup Project in the Solution Explorer -> Add -> File... select any file that ends with .config.
3) Download MSBuild Community Tasks Project, I recomend the msi installer.
4) Unload your project (the csproj) and replace the code from the other question with this one:
Code:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
<!-- Generate transformed app config in the intermediate directory -->
<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
<!-- Force build process to use the transformed configuration file from now on. -->
<ItemGroup>
<AppConfigWithTargetPath Remove="app.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
<PropertyGroup>
<SetupProjectPath>$(MSBuildProjectDirectory)\$(IntermediateOutputPath)$(TargetFileName).config</SetupProjectPath>
</PropertyGroup>
<!-- Change the following so that this Task can find your vdproj file -->
<FileUpdate Files="$(MSBuildProjectDirectory)\..\Setup1\Setup1.vdproj"
Regex="(.SourcePath. = .8:).*\.config(.)"
ReplacementText="$1$(SetupProjectPath.Replace(`\`,`\\`))$2" />
<FileUpdate Files="$(MSBuildProjectDirectory)\..\Setup1\Setup1.vdproj"
Regex="(.TargetName. = .8:).*\.config(.)"
ReplacementText="$1$(TargetFileName).config$2" />
</Target>
5) The previous code must be changed, so that it can find your vdproj file. I have placed a comment in the code, indicating where you need to make the change.
Now, everytime you build your main project, the MSBuild will change the Setup project, so that it uses the correct app.config file. It may have drawbacks, but this solution can be polished and become better. If you need leave a comment, and I'll try to respond ASAP.
Resources I Used
MSBuild 4.0 is needed because I need to use String's Replace function, to replace single "\" to double "\" in the path. See
MSBuild Property Functions for details about using function in MSBuild.
I learned about the FileUpdate Task in this other question. The official project is MSBuild Community Tasks Project.
These two topics were important to my findings:
Trying to include configuration specific app.config files in a setup project
Problems with setup project - am I thick?
Another solution I've found is not to use the transformations but just have a separate config file, e.g. app.Release.config. Then add this line to your csproj file.
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<AppConfig>App.Release.config</AppConfig>
</PropertyGroup>
This will force the deployment project to use the correct config file when packaging.
I combined the best of the following answers to get a fully working solution without using any external tools at all:
1. Setup App.Config transformations
Source: https://stackoverflow.com/a/5109530
In short:
Manually add additional .config files for each build configuration and edit the raw project file to include them similar to this:
<Content Include="App.config" />
<Content Include="App.Debug.config" >
<DependentUpon>App.config</DependentUpon>
</Content>
<Content Include="App.Release.config" >
<DependentUpon>App.config</DependentUpon>
</Content>
Then include the following XML at the end of the project file, just before the closing </project> tag:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="AfterCompile" Condition="exists('app.$(Configuration).config')">
<TransformXml Source="app.config" Destination="$(IntermediateOutputPath)$(TargetFileName).config" Transform="app.$(Configuration).config" />
<ItemGroup>
<AppConfigWithTargetPath Remove="app.config" />
<AppConfigWithTargetPath Include="$(IntermediateOutputPath)$(TargetFileName).config">
<TargetPath>$(TargetFileName).config</TargetPath>
</AppConfigWithTargetPath>
</ItemGroup>
</Target>
Finally edit the additional .config files to include the respective transformations for each build configuration:
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!-- transformations here-->
</configuration>
2. Include the appropriate .config in the setup project
First, add a command in the postbuild event of your main project to move the appropriate transformed .config file to a neutral location (e.g. the main bin\ directory):
copy /y "$(TargetDir)$(TargetFileName).config" "$(ProjectDir)bin\$(TargetFileName).config"
(Source: https://stackoverflow.com/a/26521986)
Open the setup project and click the "Primary output..." node to display the properties window. There, add an ExludeFilter "*.config" to exclude the default (untransformed) .config file.
(Source: https://stackoverflow.com/a/6908477)
Finally add the transformed .config file (from the postbuild event) to the setup project (Add > File).
Done.
You can now freely add build configurations and corresponding config transforms and your setup project will always include the appropriate .config for the active configuration.
I accomplished this in a different manner with no external tools:
I added a post-build event that copied the target files to a 'neutral' directory (the root of the /bin folder in the project) and then added this file to the .vdproj. The deployment project now picks up whatever the latest built version is:
Post Build Command:
copy /y "$(TargetDir)$(TargetFileName).config" "$(ProjectDir)bin\$(TargetFileName).config"
This worked for what I needed without any external tools, and works nicely with SlowCheetah transformations.
Based off Alec's answer, here is a similar element that you can use along with the transformations and still get their full benefit:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Content Include="$(OutputPath)$(AssemblyName).dll.config">
<InProject>false</InProject>
<Link>$(AssemblyName).dll.config</Link>
</Content>
</ItemGroup>
This way, you can use the SlowCheetah transforms or the built-in ones to transform your .config file, and then go into your Visual Studio Deployment Project (or other) and include the Content from the affected project in your Add -> Project Output... page easily, with minimal changes.
None of the above solutions or any articles worked for me in deployment/setup project. Spent many days to figure out the right solution. Finally this approach worked for me.
Pre requisites
I've used utility called cct.exe to transform file explicitly. You can download from here
http://ctt.codeplex.com/
I've used custom installer in setup project to capture installation events.
Follow these steps to achieve app config transformation
1) Add your desired config files to your project and modify your .csproj file like these
<Content Include="app.uat.config">
<DependentUpon>app.config</DependentUpon>
</Content>
<Content Include="app.training.config">
<DependentUpon>app.config</DependentUpon>
</Content>
<Content Include="app.live.config">
<DependentUpon>app.config</DependentUpon>
</Content>
I've added them as content so that they can be copied to output directory.
2) Add cct.exe to your project which you downloaded.
3) Add custom installer to your project which should look like this
[RunInstaller(true)]
public partial class CustomInstaller : System.Configuration.Install.Installer
{
string currentLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string[] transformationfiles = Directory.GetFiles(Path.GetDirectoryNam(Assembly.GetExecutingAssembly().Location), "app.*.config");
public CustomInstaller()
{
InitializeComponent();
// Attach the 'Committed' event.
this.Committed += new InstallEventHandler(MyInstaller_Committed);
this.AfterInstall += new InstallEventHandler(CustomInstaller_AfterInstall);
}
void CustomInstaller_AfterInstall(object sender, InstallEventArgs e)
{
try
{
Directory.SetCurrentDirectory(currentLocation);
var environment = Context.Parameters["Environment"];
var currentconfig = transformationfiles.Where(x => x.Contains(environment)).First();
if (currentconfig != null)
{
FileInfo finfo = new FileInfo(currentconfig);
if (finfo != null)
{
var commands = string.Format(#"/C ctt.exe s:yourexename.exe.config t:{0} d:yourexename.exe.config ", finfo.Name);
using (System.Diagnostics.Process execute = new System.Diagnostics.Process())
{
execute.StartInfo.FileName = "cmd.exe";
execute.StartInfo.RedirectStandardError = true;
execute.StartInfo.RedirectStandardInput = true;
execute.StartInfo.RedirectStandardOutput = true;
execute.StartInfo.UseShellExecute = false;
execute.StartInfo.CreateNoWindow = true;
execute.StartInfo.Arguments = commands;
execute.Start();
}
}
}
}
catch
{
// Do nothing...
}
}
// Event handler for 'Committed' event.
private void MyInstaller_Committed(object sender, InstallEventArgs e)
{
XmlDocument doc = new XmlDocument();
var execonfigPath = currentLocation + #"\yourexe.exe.config";
var file = File.OpenText(execonfigPath);
var xml = file.ReadToEnd();
file.Close();
doc.LoadXml(FormatXmlString(xml));
doc.Save(execonfigPath);
foreach (var filename in transformationfiles)
File.Delete(filename);
}
private static string FormatXmlString(string xmlString)
{
System.Xml.Linq.XElement element = System.Xml.Linq.XElement.Parse(xmlString);
return element.ToString();
}
}
Here I am using two event handlers CustomInstaller_AfterInstall in which I am loading correct config file and transforming .
In MyInstaller_Committed I am deleting transformation files which we don't need on client machine once we apply has been applied. I am also indenting transformed file because cct simply transforms elements were aligned ugly.
4) Open your setup project and add project output content file so that setup can copy config files like app.uat.config,app.live.config etc into client machine.
In previous step this snippet will load all available config files but we need supply right transform file
string[] transformationfiles = Directory.GetFiles(Path.GetDirectoryNam
(Assembly.GetExecutingAssembly().Location), "app.*.config");
For that I've added UI dialog on setup project to get the current config. The dialog gives options for user to select environment like "Live" "UAT" "Test" etc .
Now pass the selected environment to your custom installer and filter them.
It will become lengthy article if I explain on how to add dialog,how to set up params etc so please google them. But idea is to transform user selected environment.
The advantage of this approach is you can use same setup file for any environment.
Here is the summary:
Add config files
Add cct exe file
Add custom installer
Apply transformation on exe.config under after install event
Delete transformation files from client's machine
Modify setup project in such a way that
set up should copy all config files(project output content) and cct.exe into output directory
configure UI dialog with radio buttons (Test,Live,UAT..)
pass the selected value to custom installer
Solution might look lengthy but have no choice because MSI always copy app.config and doesn't care about project build events and transformations. slowcheetah works only with clickonce not setup project
The question is old, but the following could still help many folks out there.
I would simply use Wix WiFile.exe to replace the concerned file in the msi this way (for the sake of this example, we call your msi yourPackage.msi):
Step 1. From command prompt run: WiFile.exe "yourPackage.msi" /x "app.exe.config."
The above will extract the "wrong" app.exe.config file from the msi and place it the same directory as your msi;
Step 2. Place the new (prod) config file (must have the same name as the extracted file: app.exe.config) in same location as your msi;
This means that you are overwritting the app.exe.config that has just been extracted in step 1 above, with your new (production config file);
Step 3. From command prompt run: WiFile.exe "yourPackage.msi" /u "app.exe.config."
THAT'S ALL!
The above can be done in a few seconds. You could automate the task if you wanted, for instance, by running it as batch or else.
After running step 3 above, your msi will contain the new config file, which will now be installed at your clients' when they run the setup.

Resources