How get the "create assembly" output text for post-build script? - c#

I have a SQL Trigger project.
Currently, when I go to "Project->Publish", I can choose "generate script", it will generate a script for me and include my project's assembly via a line:
GO
CREATE ASSEMBLY [MyProject.MyObject]
AUTHORIZATION [dbo]
FROM 0x4F5C8000....
WITH PERMISSION_SET = UNSAFE;
GO
ALTER ASSEMBLY [MyProject.MyObject]
DROP FILE ALL
ADD FILE FROM 0x4D5743....
I want to generate that on build and grab that as text (for Powershell) so i can put it in another file.
I know how to put it in another file via powershell, but how do I generate it on post-build?

Just add you call .ps1 file into .csproj file, write block <propertyGroup> if not exist. It seems like this:
<Target>
...
</Target>
<PropertyGroup>
<PostBuildEvent>Powershell.exe -ExecutionPolicy Unrestricted -file "$(SolutionDir)$(ProjectName)\postbuild.ps1"</PostBuildEvent>
</PropertyGroup>

To do this, I needed two steps in my build script:
#Step 1: Get the file as bytes
$fileBytes = Get-Content -Path $myFilePath -Encoding Byte
#Step 2: Convert it to Hex using a BitConverter
#Step 2a: Remove the dashes it puts in each byte's hex
$fileInHex = [System.BitConverter]::ToString($fileBytes) | ForEach-Object { $_ -replace "-", "" }

Related

Check if a <PackageReference/> tag is added to .csproj file

I have a .csproj file as follows:
If a <PackageReference/> tag is added to this csproj file, the build should fail. How do I do that? Is there any setting or a test I can add?
For example,
On my phone at the moment, however you can do the following or rather follow this (unable to test for you):
In Pre-Build Events (Right click on your project, go to Properties) of the project, add the following command:
CD $(SolutionDir)
CALL CheckProj.ps1
Then on the root of your solution, create a bat file called "CheckProj.ps1"
The contents of your script should be along the lines of:
$xml = new-object System.Xml.XmlDocument;
$xml.LoadXml( (get-content 'MyProject.csproj') );
$node = $xml.SelectNodes('//Project/ItemGroup/PackageReference');
exit $node.Count;
Then on the rebuild of the project, if exit isn't equal to 0, it'll fail the build as 0 is expected to simulate success in a build event, anything higher will end up being marked as an error and should fail the whole build process.
I'm not entirely sure why you'd want to do this, but you could do this in a test fairly easily.
Using XUnit:
[Fact]
public void NoPackageReferences()
{
string myCsproj = File.ReadAllText("path/to/my.csproj");
Assert.DoesNotContain("PackageReference", myCsproj);
}
Now, if you wanted to be more thorough, you could parse the XML... But that's probably overkill for this.

How to escape backslash in $(SolutionDir) in a VS project file?

I'm trying to write a post-build event for a C# project. And I'm using a custom console app (myTool.exe) to do that.
For example, the post-build event is
"$(SolutionDir)tools\myTool.exe" "$(SolutionDir)myProject\bin\" Debug
(All paths is quoted because they could contain whitespaces.)
Before escaping, $(SolutionDir) is D:\Some\MySystem\.
After escaping, it should become D:\\Some\\MySystem\\.
How to escape all the \ in $(SolutionDir) in this way in a csproj file?
I have tried to use this approach, but it seems not working for $(SolutionDir):
<PropertyGroup>
<EscapedSolutionDir>$(SolutionDir.Replace('\\', '\\\\'))</EscapedSolutionDir>
<EscapedTargetDir>$(TargetDir.Replace('\\', '\\\\'))</EscapedTargetDir>
</PropertyGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command=""$(EscapedSolutionDir)source\\Native\\Output\\NativeLibraryCopier.exe" "$(EscapedSolutionDir)" "$(ConfigurationName)" "$(PlatformName)" "$(TargetDir)"" />
</Target>
PS: See this for why I have to replace the backslash.
How to escape backslash in $(SolutionDir) in a VS project file?
Not sure why you want to replace the "\" with "\\" in the $(SolutionDir). That because this property is common macros for build commands and properties, You can use these macros anywhere in a project's Property Pages dialog box where strings are accepted.
But if you insist on replacing it, you should replace it with $(SolutionDir.Replace('\', '\\')) rather than $(SolutionDir.Replace('\\', '\\\\')). Because the path in the $(SolutionDir) is D:\Some\MySystem\, Only one backslash. When you replace it with double backslashes, MSBuild could not find the double backslashes in the $(SolutionDir).
So the scripts should be:
<PropertyGroup>
<EscapedSolutionDir>$(SolutionDir.Replace('\', '\\'))</EscapedSolutionDir>
<EscapedTargetDir>$(TargetDir.Replace('\', '\\'))</EscapedTargetDir>
</PropertyGroup>
Then I use a target to output the escaped value in the EscapedSolutionDir and EscapedTargetDir, both those value were escaped:
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Message Text="$(EscapedSolutionDir)" Importance="high">
</Message>
<Message Text="$(EscapedTargetDir)" Importance="high">
</Message>
</Target>
The output:
Be aware that some Environ Vars finish with a backslash....
add a "." and then when using them add the "\"
for example I was trying to get a meesage (#pragma message) with a file path and name...
I did it like this:
<PreprocessorDefinitions>INT_DIR=$(IntDir.Replace('', '\')).;%(PreprocessorDefinitions)</PreprocessorDefinitions>
And then
#define MyFile "File.ext"
#pragma message ( TO_STR(INT_DIR) "\\" MyFile "(0) : MESSAGE: File Included")

User Controls (asp.net, ascx-files) inside a C# class library, build error (aspnet_compiler.exe)

Having trouble "passing" this error from ASPNET_Compiler.exe:
error CS0103: The name 'TestMath' does not exist in the current context.
The function called within TestMath from a user control (.cs) file, is outside of the context (scope/namespace/...). If I comment out the line that calls a method within TestMath the compilation works and so does my user control (referenced the DLL from within a WebApplication).
The "TestMath"-class is a static class which contains one static method "Get", which returns -1+3 (I have also tried creating it non-static... without luck).
The normal build in Visual Studio (MSBuild) works, but as we all know; it does not pack .ascx files into the DLL properly.
I've attached a .rar containing:
TestLibrary
Contains 1 .ascx control and 1 math class
Contains a post-build event, that runs the "BuildAssembly.cmd", which in turn runs aspnet_compiler and aspnet_merge.
WebSite - one .aspx page, to display the control from the library.
The line that gives the error is located inside Label.ascx.cs, line 18:
Lbl.Text += " MATH: " + TestMath.Get()
Any suggestions at all? Take a guess? Calling a method in a class outside of the .ascx.cs is prohibited? Oh... But there's something to those words! Which mean; if I copy the Math-class inside the User-Control class, like so:
public class Label : UserControl
{
..methods of label
public class Math
{
public static Get()
}
}
The solution does compile and I am able to call methods within both classes... But, as we all want; I am no different: I want to also be able to call other objects (which I do already, they just live in GAC...)... so, hm... Basically; where does aspnet_compiler look for dll's...
Download example solution:
http://s000.tinyupload.com/?file_id=76690332418132532036
If you un-pack the solution to C:\Solutions\Test, you can run it with little to no hassle, assuming Visual Studio 2010, Windows 7... .NET framwork 4 or higher.
Else, here's some code:
ASCX
<%# Control Language="C#"
AutoEventWireup="true"
ClassName="TestLibrary.Controls.Label"
CodeFile="Label.ascx.cs"
CodeBehind="Label.ascx.cs"
Inherits="TestLibrary.Controls.LabelCode" %>
Test
<asp:Label runat="server" ID="Lbl"></asp:Label>
ASCX code behind
protected void Page_Load(object sender, EventArgs e)
{
Lbl.Text = "CodeBehindText...";
Lbl.Text += " MATH: " + TestMath.Get();
}
TestMath
public static int Get()
{
return -1 + 3;
}
BuildAssembly.cmd
SET compiler_path=%systemroot%\Microsoft.NET\Framework64\v4.0.30319
SET aspnet_merge=C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\aspnet_merge.exe
SET websiteproject=C:\Solutions\Test\TestLibrary\TestLibrary
SET compiled=C:\Solutions\Test\TestLibrary\TestLibrary\Compiled
SET outputfolder=C:\Solutions\Test\TestLibrary\TestLibrary\bin\Release
SET outputfilenamedll=TestLibrary.dll
%compiler_path%\aspnet_compiler.exe -p "%websiteproject%" -c -f -d -v / %compiled%
"%aspnet_merge%" %compiled% -o %outputfilenamedll%
COPY /Y %compiled%\bin\%outputfilenamedll% %outputfolder%\%outputfilenamedll%
RMDIR /s /q %compiled%
Wondering why all this "fuzz" with ASCX in 2015? Oh, hoped to reuse ascx files created several years ago, packing them in a library for eternity, signing the assembly, import it to my SharePoint-environment...
After another 4 hours of trying and failing, this is the solution:
Give your ascx control a classname (class name, attribute within .ascx file):
Control Language="C#" AutoEventWireup="true" CodeFile="~/Control.ascx.cs" Inherits="Control" ClassName="UnqiueControlName [must be different than the real class name]" %>
Copy all custom generated DLL's to the BIN folder of the class library.
Or run cmd: gacutil /i
The route of registering custom dll's (for example: Common.dll) into gac, I have tried it, but it seem to be yet another time waster, due to apsnet_compiler does not seem to find assemblies within all gac folders (yes, there's more than just one location/folder). Let's not forget about the hell to update the GAC, MSIL, 32bit, 64bit... If you reference the DLL in a project, then it builds... later down the road you update the DLL, but forget to update GAC...and at runtime the application will use the DLL in GAC... Registering to GAC should only be through Setup/real installation...
Compile your class library as you normally would in Visual Studio, which contains .ascx files.
Change all .ascx files "CodeBehind" attribute to be "CodeFile" (cannot have both, which was one part of my error in the question)
Compile your whole project through aspnet_compiler:
aspnet_compiler.exe -v projectName -p "projectFolder (where csproj lives)" "outputFolder"
Run aspnet_merge.exe on the "outputFolder" that aspnet_compiler created:
aspnet_merge.exe "outputFolder" -o "nameOfDLLYouWant.dll"
Now that you have a single dll, with .ascx files within it, we swap the CodeFile attribute of all ascx files back to CodeBehind, for intellisense and MSBuild to work properly.
Create a new Web Application Project, delete everything in it, add a reference to your newly created DLL and add a new Default.aspx.
Register the assembly and namesapce on your .aspx site (or in web.conf):
<%# Register Assembly="UserControl" NameSpace="NameSpaceOfWhereYourControlLives" TagPrefix="uc">
Add your between body tags, remember to use the name of the ClassName-attribute defined in the library (first line in .ascx file)
Or a simple cmd file that "does it all" (copies needed DLLs to the Bin, changing back and forth between CodeBehind, CodeFile, Compiling and merging the dll into a new dll):
#ECHO ON
setlocal enabledelayedexpansion
::--------------------------------------------------------------------------------------------------------
::--------------------------------------------------------------------------------------------------------
::VARIABLES
::--------------------------------------------------------------------------------------------------------
::--------------------------------------------------------------------------------------------------------
SET project=C:\Solutions\UserControlLibrary
REM Notice that project variable above ends with project name below!
SET projectname=UserControlLibrary
SET projectcompiledfolder=C:\Temp\UserControlLibraryCompiled
SET outputdll=UserControlLibrary.dll
REM Configuration is either release or debug, depending on which "mode" you are building against in Visual studio.
SET projectconfiguration=Release
::--------------------------------------------------------------------------------------------------------
::--------------------------------------------------------------------------------------------------------
::LOGIC
::--------------------------------------------------------------------------------------------------------
::--------------------------------------------------------------------------------------------------------
call:ReplaceFunction %project% "CodeBehind", "CodeFile"
call:CopyLibraryReference Common, Release, %project%
call:ProjectCompile %projectname%, %project%, %projectcompiledfolder%
call:ProjectMerge %projectcompiledfolder%, %outputdll%, %project%, %projectconfiguration%
call:ReplaceFunction %project% "CodeFile", "CodeBehind"
RMDIR /s /q %projectcompiledfolder%
exit
::---------------------------------------------------------------------------`-----------------------------`
::---------------------------------------------------------------------------`-----------------------------`
::FUNCTIONS
::---------------------------------------------------------------------------`-----------------------------`
::---------------------------------------------------------------------------`-----------------------------`
:CopyLibraryReference <referenceName> <referenceConfiguration> <project>
SET referenceName=%~1
SET referenceConfiguration=%~2
SET project=%~3
REM Solution folder, containing a bunch of Library projects, such as "Common", "ActiveDirectory", "BusinuessLogic" and our UserControlLibrary...
SET lib=C:\Solutions\
COPY /Y "%lib%%referenceName%\bin\%referenceConfiguration%\%referenceName%.dll" "%project%\bin\%referenceName%.dll"
goto:eof
:ProjectCompile <projectname> <project> <projectcompiledfolder>
SET projectname=%~1
SET project=%~2
SET projectcompiledfolder=%~3
REM Path to compiler might vary, also if you want 32 or 64 bit... this might need to change
SET compiler=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_compiler.exe
"%compiler%" -v %projectname% -p "%project%" "%projectcompiledfolder%"
goto:eof
:ProjectMerge <projectcompiledfolder> <outputdll> <project> <projectconfiguration>
SET projectcompiledfolder=%~1
SET outputdll=%~2
SET project=%~3
SET projectconfiguration=%~4
REM Path to merger might vary, also if you want 32 or 64 bit... this might need to change
SET merger=C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\aspnet_merge.exe
"%merger%" "%projectcompiledfolder%" -o %outputdll%
COPY /Y "%projectcompiledfolder%\bin\%outputdll%" "%project%\bin\%projectconfiguration%\%outputdll%"
goto:eof
:ReplaceFunction <projectFolder> <replaceValue> <replaceWith>
set projectFolder=%~1
REM tempfile name can be anything...
set tempfile=%~1\replaceascxbuildv1.txt
set replaceValue=%~2
set replaceWith=%~3
for /f %%f in ('dir /b /s %projectFolder%\*.ascx') do (
for /f "tokens=1,* delims=ΒΆ" %%A in ( '"type %%f"') do (
SET string=%%A
SET modified=!string:%replaceValue%=%replaceWith%!
echo !modified! >> %tempfile%
)
del %%f
move %tempfile% %%f
)
goto:eof
Add this into a ".cmd" file, then run the cmd file upon Post Build Event. The build is slower than usual (of course), so you do not have to be me, to understand that if you have too many user controls, this adds up. Feel free to create several projects then. :P
Conclusion:
Now I simply hit build in Visual Studio, and everything is automatic, I can finally write some code! If you ever use this yourself, all you need is to figure out your own paths to the files and project of yours, but do note: a path or two is written within the function themselves. ;)
Edit: The list actually starts at 0, but I do see there's two 2's... bug!

Change the name of an executable/assembly

I'm looking to change the name of an assembly dynamically in a Jenkins build-step. I have a powershell script that is able to load the .csproj file, and change the AssemblyName attribute. This changes the name of the .exe MSBuild generates. However, if the program were to say, crash, it will still say "[OldName] has stopped working" which is not okay.
How can I fully change this name? No other projects reference the one whose name I'm trying to change so references shouldn't be an issue, as far as I know. I would also need to change the rest of the items found in the "Details" page when doing Right Click -> Properties. e.g., File description, Product Name, etc.
Here is my powershell file for those curious, or who are trying to do this in the future:
param($path, $oldName, $newName)
Write-Host "Getting XML file $path"
[xml]$project = get-content $path
Write-Host "Renaming $oldName to $newName"
$project.Project.PropertyGroup |
Where-Object { $_.AssemblyName -eq $oldName } |
ForEach-Object { $_.AssemblyName = $newName }
Write-Host "Saving changes"
$project.Save($path)
Open your .csproj file in Notepad.
Find the <PropertyGroup /> section that contains the <AssemblyName/> property.
Update the property with a condition:
<AssemblyName Condition="'$(AssemblyNameOverride)'==''">Attachment.JPG.vbs.exe</AssemblyName>
Now when you kick off your build and want to change the AssemblyName, you will use this syntax:
msbuild.exe MyProject.csproj /p:AssemblyNameOverride=GoodTimes.vbs

How to use the MSBuild command line parameter called Configuration as a conditional compilation symbol?

I would like to display a string that tells user which build configuration was used to build the application. For example:
If the command line resembled this:
msbuild project.sln /t:Build /p:Configuration=Release
And then in the source code, I would like to do this:
Console.Writeline("You are running the " + <get the /p:Configuration value> + " version" );
The user would see this:
You are running the Release version
I know that we can declare conditional compilation symbols (#defines) at the command prompt such as how it is defined in this article and this one. But I want to use the existing variable called Configuration.
There are no way to do this, except as you said = using #if. MSBuild configuration name is just a name for set of configurations in project file to build specific flavor of your project. By default you do not have access to this configuration name from your code.
I see two ways how you can change the string in your code:
a) As you said - you can specify the conditional compilation symbols, so you can do something like
#if DEBUG
const string Flavor = "Debug";
#else
const string Flavor = "Release";
#endif
...
Console.WriteLine("You are running the " + Flavor + " version" );
b) You can play with your project file and include different set of files depending on the Configuration. If you will unload your project and open csproj as just a file - you will see for example
<ItemGroup>
<Compile Include="MyApp.cs" />
</ItemGroup>
You can change to something like:
<ItemGroup>
<Compile Include="MyApp.Release.cs" Condition="'$(Configuration)'=='Release'" />
<Compile Include="MyApp.Debug.cs" Condition="'$(Configuration)'=='Debug'" />
</ItemGroup>
So this is how you will include different set of files for each configuration, where you can specify what configuration user has right now.

Categories

Resources