VSTS Task that changes project files and checks in the changes - c#

With TFS 2015, we had made a vNext (VSTS) task that would find a selected file, substitute a token (version number is where it started,) wrote out the changes to the file, and checked the file in with a comment as to the nature of the change. It did this all through automation during the build process.
We are about to upgrade to DevOps 2020 and the TFS management tools from 2015 were deprecated, which in itself would be fine, however, we still need to automate these file changes during the build process, including the check-ins noting the nature of the change.
The old task fails miserably ported over directly. I have re-written the process as a C# console app project and planned to call it during a PowerShell script, but I am encountering a number of road blocks to this plan.
What I have done so far.
I have written a task.json for the VSTS task that accepts parameters that it passes to a PowerShell script.
I have written a PowerShell script that call a C# console application to both locate and change the tokens in the file(s) as specified by the task. After changing the file content, it overwrites the original file.
I appear to have two issues that, as yet, I am unable to solve.
I am expecting (if task parameters dictate,) to alter the pipeline environment variable $env:BUILD_BUILDNUMBER to a new value, from the C# code. I am using the following C# command using the third parameter expecting that this will allow the pipeline to see the changes it has made to the variable: Environment.SetEnvironmentVariable("BUILD_BUILDNUMBER", buildNumber, EnvironmentVariableTarget.Machine ); (I have also tried without the parameter and also User and Process, to no avail.) The variable will not 'set' for the pipeline to see outside of the console app.
I need to check the changes made in step 2 from the C# code, back into the code repository with a short comment. The initial 'Get' that the pipeline does calls tf, but I have not had the same success. I have found that if I call VS2019's copy of tf.exe or the agents copy in 'externals' on the build machine, whether from C# or from the later PowerShell script, I get "##[error]Unable to determine the workspace. You may be able to correct this by running 'tf workspaces /collection:TeamProjectCollectionUrl'" Needless to say, this later instruction to run tf workspaces does not help out.
I am hoping these things have simple solutions. I have searched for, but not found an API call to DevOps 2020 that will check in the code. Perhaps that does not exist. As for the Environment Variable, I am at a little bit of mystery as to why the EnvironmentVariableTarget.Machine is not working. I suspect it has something to do with the ##vso[] method not being called but I am unsure of how to pass out my findings from the console app (which also needs to return 0 for success, else failure,) to the PowerShell script that can change the variable more easily.
If there are any bright ideas out there about this, I would really appreciate the insight you might lend. I have been at this for a while and I am not sure what else to consider to make this work.

Using TFVC rather than Git, and a multitude of other little legacy technology items, makes working with the new tools a bit harder to figure out as it was in this case.
So my first problem item, updating the environment Build Number in the build tasks before the build starts. I was able to set the variable from my C# code, by opening a PowerShell process that takes an argument to update the variable with the ##vso[Build.UpdateBuildNumber] function in PowerShell. The C# code to do this looks like the following:
var ps = #$"C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe";
var script = $#"SetEnvironmentBuildNumber.ps1";
if (File.Exists( script )){
if ( File.Exists( ps ) )
{
await Task.Run( () =>
{
var scriptWithArguments = string.Concat( Path.Combine( Directory.GetCurrentDirectory(), script ), " -newBuildNumber ", newBuildNumber);
Process.Start( ps, scriptWithArguments );
} );
}
else
{
Console.WriteLine( $"PowerShell File Not found at: {ps}" );
}
}
else
{
Console.WriteLine( $"The current directory is: {Directory.GetCurrentDirectory()}");
Console.WriteLine($"Script File Not found at: {script}");
}
The PowerShell script in SetEnvronemntBuildNumber.ps1 looks like this:
[CmdletBinding()]
param(
[string][Parameter(Mandatory=$true)]$newBuildNumber
)
Write-Host( "Setting the new build number - " + $newBuildNumber )
Write-Host( "##vso[Build.UpdateBuildNumber]$newBuildNumber" )
As it turns out, this exercise helped me understand the Undetermined Workspace, my problem #2.
If you look at the if statements of my C# code, you'll note a check to see just what directory is the current working directory. Finding out that my new task changed the current working directory to the location of my Task and not the pipeline's working directory gave me reason to pause.
Even though, I fall out of the C# code, back into my PowerShell script, I am not looking at the solution's directory, I am looking at the Task directory. If I call tf from this view, there is no source code, and hence no way to determine the WorkSpace.
The solution was simple, there is a pipeline System environment variable that will get me back home, so to speak. Calling the PS Set-Location as below, just before I call tf vc checkin... allowed tf to find the Workspace and save my changes.
Set-Location $env:System_DefaultWorkingDirectory
This was all it took, all these tweaks were innocent enough, but very troublesome to diagnose without any formal training in the ways of the DevOps pipeline. I look forward to more 'fun' with DevOps, when we try to figure out how to get to Git from TFVC... I am sure not all our legacy will see such significant changes.
Thanks for the suggestions, and I hope this can help some other poor soul trapped in a legacy world and trying to get out of the hole.

Related

Looking to launch a local exe on a remote machine without having the resurces on the remote machine

So I have built some code, it's quite simple basically it stops all active input from keyboard and mouse until a text file of a certain name appears in the C:\Temp directory. It also has a manifest file to run it as administrator on start up.
So I found something that on the surface looks like it fulfils my needs of being able to do this task however upon running it I found out that the project has been compiled in x86 and does not run on my x64 machine. Here is the reference to the project if anyone would like to look into it, it's a very smartly designed piece of code that does an interesting objective. It also explains clearly enough what I am trying to accomplish.
So after implementing this (and failing) I have setup a couple other avenues to try, one is VBA through excel with the VBA copying itself to and from the machines in a list and running itself, then there is using VBS to write the entire code as a txt file on the target machine change the extension and then execute it remotely. I have just started researching these but I imagine the problems of running as an administrator amongst other things will crop up again to be dealt with. To be honest though I would really prefer to do this in C# only as that is the language I'm trying to go further in so I'm interested in this challenge. If anybody knows of a similar library of code or application I could look into to achieve what I'm trying I would appreciate being pointed in the right direction.
I would try and be more specific about what libraires/API's im trying to implement but the truth is I don't know what libraries I need to even interact with to get what I want. My goal is to have C# executable code on my machine and a tool that can run that executable on another machine.
Thanks
Thanks to the help in comments from #Nick.McDermaid I was able to correctly open and build the project I was trying to download. Unsure what caused the issue previously with me not being able to open and interact with the code but now I have it I shall pursue this avenue further to accomplish my goal.
As an addendum one other avenue I tried for executing code remotely was through VBS where I used
set svcproc=getobject("winmgmts:{impersonationLevel=impersonate}!\\"&MachineName & "\root\cimv2:win32_process")
scmd="""C:\Program Files\Internet Explorer\iexplore.exe"" -framemerging ""https://gifyu.com/images/Boo-Ghost-Gif.gif"""
'scmd="C:\Windows\notepad.exe"
iret=svcproc.create(scmd,null,null,pid)
set svcproc=nothing
to execute something that existed on the remote machine but I ran into a LOT of security policy issues where I could launch the process but I couldn't bring it to the foreground as the Malware tracker on the machine thought it was an attack and quashed it immediately.

TFS Deploy WITHOUT using WebDeploy

I'm trying to setup our CI build environment and having an issue.
First, I'm using VS and TFS 2012 so I can't use the *.12.xaml templates since those are for VS/TFS 2013.
Second, right now I'm configured to use just the defaulttemplate.11.xaml. Originally, I was using WebDeploy for the deployment method and that was working great. Since then, our web/server team has re-configured our test environment to use IIS Shared Configuration as well as DFS Replication to keep everything in sync.
Because of that, I'm no longer able to use WebDeploy (I passed this post over to the TFS admins, but they said no).
Is there a place where I can add some msbuild arguments, or a post-build event where I can send a *.cmd file with some arguments so I can get my code copied/deployed?
I've read Hanselman's (and everyone else that copied him) posts/blogs that say "if you're using xcopy, you're doing it wrong, etc...", but I believe in my case I CAN'T use Web Deploy.
Update:
So I thought I found my answer. Since the web deploy doesn't work for me, I found a workflow activity called CopyDirectory that sounded exactly like what I need.
I went through the process of updating my default template to add this additional step to the build process, which by the way, does NOT work very well. After adding the step, saving, etc, the step doesn't ever show up in my build output. I gave up for awhile to go see if I could do this on our Jenkins build server, got some different errors over there so I came back to TFS to make the changes and commit. Since the CI was still setup in TFS (granted, failing), I noticed that a build got kicked off when I made my commit. I decided to watch for awhile and IT FINISHED SUCCESSFULLY! Woah, all right. So I checked through the build logs, and find out that it threw a WARNING saying "failed to copy. Ensure the source directory exists and that you have the appropriate permissions".
Well, since I just entered this value incorrectly, no big deal, just change to the correct BuildDetail.DropLocation, and we should be golden.
WRONG, after building again with my changes to the source and destination values, I come to find out that since I'm trying to deploy my files to a different domain, it still fails.
Oh, and in addition to that, YOU CAN'T PASS CREDENTIALS TO THE COPYDIRECTORY STEP! REALLY! Phew, I found some documentation though, it says "give the tfs build service/account permissions on the domain that you want to copy to. Well, that would be great, if my server team would allow that, but they don't.
Back to square one...(this is going to turn into a blog about me complaining about TFS...)
I believe you can do it using robocopy. You will want to update your build template to include a new InvokeProcess activity. Set the activity's FileName to "RoboCopy" (include the quotes) and it's Arguments to something like the following:
String.Format(" ""{0}"" ""{1}"" /E /R:10 /W:10 /NFL /NDL ", BinariesDirectory, BuildDetail.DropLocation)
Of course changing the robocopy flags to your specific needs.
I don't think you can pass credentials into robocopy either though, so you might still be SOL there.
One possible alternative though is that because your admins won't give the TFS Build User (i.e. tfsservice) permissions on the destination box, you could change the TFS Builds to run as a different User that does have permissions on that box. To do this I believe you just have to log onto your TFS Build machine, go to the Services, find the Visual Studio Team Foundation Build Service Host 2012 (or something similar), and change the Log On As user from tfsservice to whatever user has permissions on the box that you want to publish to. Of course you will also need to give that user permissions to do everything else that the build system needs to do (download source code, etc.).

IronPython sys._getframe not found

I'm currently building a program in C# which will call functions in provided python script files.
Some of these script files calls _getframe() in sys, which results in the error:
System.MissingMemberException: 'module' object has no attribute
'_getframe'
(Since IronPython doesn't have _getframe activated by default.)
I have done quite a lot of googling and found out that you can activate it in ipy.exe by providing -X:Frames as a command line option, however this doesn't solve my problem since I'm not directly using ipy.exe to execute the python code.
In this thread they mention rebuilding IronPython from source with the command line options, I downloaded the source files but have no idea how to build it with those options.
Also they mention that the options are in the official installer, I have run the installer exe several times but haven't seen a glimpse of those options there.
When creating the PythonEngine you can pass a dictionary of options; you just need to set the "Frames" and/or "FullFrames" keys in the dictionary to true:
var options = new Dictionary<string, object>();
options["Frames"] = true;
options["FullFrames"] = true;
ScriptEngine engine = Python.CreateEngine(options);
If you don't want FullFrames, just leave it out or set it to false.
A little out of the scope of the question, but meant for anyone else getting this error by invoking a Python script using the ipy.exe interpreter directly.
You can just add the argument -X:FullFrames. So for example invoke the script like
ipy.exe -X:FullFrames script.py

Free solution for automatic updates with a .NET/C# app?

From searching I can see this has been asked time and time again, but not adequately enough, so here goes. I'm a hobbyist developer with no budget. A program I've been developing has been in need of regular bugfixes, and me and users are getting tired of having to manually update.
Me, because my current solution of updating a text file through FTP and my download links on the website, and then hoping users will see the "there's an update message", and finally getting them to then be bothered to manually download the update, well quite frankly, is abysmal.
Users, because, well, "Are you ever going to implement auto-update?" "Will there ever be an auto-update feature?" And if I happen to screw up the update process, pitchforks start arriving.
Over the past I have looked into:
WinSparkle - No in-app updates, and the DLL is 500 KB. My current solution is a few KBs in the executable and has no in-app updates.
.NET Application Update Component - Unfortunately I can't comprehend the documentation.
Eduardo Olivera's AutoUpdate - This doesn't appear to support anything other than working with files that aren't in use.
wyUpdate - wyBuild isn't free, and while the wyUpdate specification is available, it's simply too complex and time-consuming to go through.
AppLife Update - Ditto the last sentence.
ClickOnce - Workarounds for implementing launching on startup are massive, horrendous and not worth it for such a simple feature. Publishing is a pain; manual FTP and replace of all files is required for servers without FrontPage Extensions.
It's quite disappointing that the situation on Windows is like this when you've got really nice and simple implementations for Mac OS X like Sparkle.
Implement it yourself! It will be fun. Create a separate application that only contains update logic i.e., fetch the files online, download them, replace local files, and show that visually to the user.
So your main application could check for updates, and if they exist it would prompt the user with the possibility to update. If the user wants to, it will run the update program, close itself (so the update can happen) and presto.
The only things you need are readily avaliable in C#, FTP access and IO.
Edit: I know it's not something terribly easy, but it's a perfect chance to learn:
How to (properly) download files, in an abstracted way that can be extended to ftp, http, etc.
How to (properly) do a simple task over many files - copying or overwriting them (this implies error handling).
Practice (because there's no "proper" way) to layer and encapsulate a piece of software.
How to deal with the OS/other software (antivirus/firewall/etc) not cooperating.
These are all things we all need to know well - If it takes some weeks to code an updater it means you were needing some weeks of learning. If you don't need to learn, time to hone your skills! If you don't know if you need, time to find out! :)
Note: I know I do need to learn better file and network I/O
Should've updated this ages ago, oops!
But anyway, I've been using SparkleDotNET for a while now and it's been working absolutely wonderfully. There's a few little bugs here and there but I've already helped get some of them squashed, and hopefully I'll be able to get rid of the others too :)
For those who have the time to run the publish functionality of Visual Studio, and whose app is relatively self-contained, and doesn't require anything like launching on startup, I'd recommend ClickOnce for sure. MetroTwit uses it and it's got a nice in-app updater interface, so it seems flexible (at least to a certain degree). For launching on startup, it's possible to do so, but methods to do so are quite hacky and don't work that well.
You can try Autoupdater.NET from GitHub I developed it my self and it works very well in my applications. You just have to add one line in your code and it's done. Also, it is open source so you can modify and use as you want.
You even can not to develop an external application but implement it as your application's module, e.g. into namespace Update, and use dynamic assembly builder to generate an exe, start it and exit app main, start it again when update will be finished.
Some more info.
There is also DDay update which is open source and is used by one of my customers. We/they are primarily interested in it in the context of a windows service at it works reasonably well for that.
For a more powerful solution, you may want to look at Google Omaha. It's what Chrome uses. You can get both in-app and automatic updates in the background when your application isn't running.
Try with MD5-Update it is absolutely free and easy no configuration need in your app only add library and publish the files.
https://github.com/jrz-soft-mx/MD5-Update/
1. Your need a web server with PHP for publish your files please include updt.exe.
2. Add index.php for make list of update files. aviable on github repository https://github.com/jrz-soft-mx/MD5-Update/blob/main/Tools/Tools.zip o create new app with this code.
<?php
$_dat = array();
$_dir=new RecursiveDirectoryIterator(".");
foreach (new RecursiveIteratorIterator($_dir) as $_itm) {
$_fil = str_replace(".".DIRECTORY_SEPARATOR, "", $_itm);
if(!is_dir($_fil) && $_fil != "index.php"){
$_dat[]=array('StrFil' => "$_fil", 'StrMd5' => strtoupper(md5_file($_fil)), 'lonSiz' => filesize($_fil));
}
}
echo json_encode($_dat, JSON_UNESCAPED_UNICODE);
?>
3. Add nuget repository at your proyect
PM> Install-Package MD5.Update
4. Call the library when your app stars, with your update folder url, update all files and download your new app on updt folder, for replace your app need updt.exe
string strUrl = "http://yourdomain.com/app/";
if (MD5Update.MD5Update.Check(strUrl, true))
{
Process.Start(AppDomain.CurrentDomain.BaseDirectory + #"updt.exe", AppDomain.CurrentDomain.FriendlyName + " " + Process.GetCurrentProcess().ProcessName);
Application.Exit();
}
5. updt.exe for replace the current app with the new app updt folder to app. aviable on github repository https://github.com/jrz-soft-mx/MD5-Update/blob/main/Tools/Tools.zip o create new app with this code.
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
List<string> lisArg = Environment.GetCommandLineArgs().ToList();
if (lisArg.Count < 2)
{
MessageBox.Show("Please provide App Excutable Name and Procees name");
Application.Exit();
return;
}
string strAppName = lisArg[1];
string strAppProcees = lisArg[2];
Process[] lisPro = Process.GetProcessesByName(strAppProcees);
foreach (Process Pro in lisPro)
{
if (Pro.Id != Process.GetCurrentProcess().Id)
{
Pro.Kill();
Thread.Sleep(1000);
}
}
string strAppMain = AppDomain.CurrentDomain.BaseDirectory + strAppName;
string strAppUpdate = AppDomain.CurrentDomain.BaseDirectory + #"updt\" + strAppName;
if (!File.Exists(strAppMain))
{
MessageBox.Show("App Excutable dosent exists");
Application.Exit();
return;
}
if (!File.Exists(strAppUpdate))
{
MessageBox.Show("App Excutable Updated dosent exists");
Application.Exit();
return;
}
File.Copy(strAppUpdate, strAppMain, true);
long fileSize = 0;
FileInfo currentFile = new FileInfo(strAppMain);
while (fileSize < currentFile.Length)
{
fileSize = currentFile.Length;
Thread.Sleep(1000);
currentFile.Refresh();
}
Process.Start(strAppMain);
}
catch (Exception Ex)
{
MessageBox.Show("An error ocurred");
File.WriteAllText(AppDomain.CurrentDomain.BaseDirectory + #"updt_" + DateTime.Now.ToString("yyyyMMddTHHmmss") + " .txt", Ex.ToString());
Application.Exit();
}
all guys are right specially look at the abatishchev reply.but i think some thing other need that guy forgot it. try to develop your project "modular".put them your code in class library as you can.so during the fix operation replace one of them.think a bout database fix.some time you need to add a column to your database table.what do you do for these cases?
a have developed an update project.in that , i have three kind of fixes.
1- BUG in program operation and need to replace a DDL file
2- Bug in program and need to update currently program executive file
3- bug or change the database and need to execute a sql server query
in a table on web host i put the version history and every time that my app start check for new version.if any update exist check for its type and download it and do the suitable action depend on the update kind and parameters
good luck dude

Showing help for a command-line utility

I have a command-line utility that gets quite a bit of downloads from my site. I'm trying to show the usage when a user uses the /? or /help parameters. I have a function called ShowUsage() that has nicely formatted text on the parameters available.
I see that ShowUsage() gets called fine from Visual Studio 2008, when I'm using the command-line parameters in the project properties. However the exe does not display the help text when run from the command-line. Here's a shortened version of ShowUsage():
private static void ShowUsage()
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Text File Splitter v1.4.1 released: December 14, 2008");
Console.WriteLine("Copyright (C) 2007-2008 Hector Sosa, Jr");
Console.WriteLine("http://www.systemwidgets.com");
Console.WriteLine("");
}
I tried a bunch of different things from my searches in Google, but none are working. I know this has to be something simple/easy, but for the life of me, I can't figure this one out.
EDIT:
The code that calls ShowUsage():
if (!Equals(cmdargs["help"], null) || !Equals(cmdargs["?"], null))
{
ShowUsage();
}
I have a class that parses the parameters into the cmdargs array. I confirmed that the parameters are in the array. It's something inside ShowUsage() that is preventing from showing the text.
I'm going to try the debug trick, and see what I find.
I'm not using Console.Out anywhere.
d03boy - Just personal preference. It makes the text less cluttered onscreen, at least to me.
EDIT #2
Ok, some more information... I'm running VS2008 in Vista Ultimate 64 bit. I just check the project properties and it is set to "Windows Application." This utility is 32 bit.
I'm going to experiment with creating a separate project in the solution that is a true console program, as some of you have advised.
Can you define "not working"? Simply not doing anything? Throwing an exception? I would expect the problem to be in the Main(...) method - i.e. ShowUsage() isn't being called. Another common issue is not rebuilding it in the correct configuration, so bin/release (or whatever) isn't updated.
Have you built the app as a console exe? i.e. is the "Output Type" = "Console Application" in project properties? It needs to be in order to access the console...
For info, I find the easiest way to do a console help screen (once it gets beyond a handful of lines) is to embed a .txt file into the exe; then I just write out the text file (perhaps still using string.Format if I want to replace tokes).
Alternatively, there is the alternative string format:
Console.WriteLine(#"
Text File Splitter v1.4.1 released: December 14, 2008
Copyright (C) 2007-2008 Hector Sosa, Jr
http://www.systemwidgets.com
");
Are you redirecting Console.Out to somewhere else?
Try throwing a System.Diagnostics.Debugger.Launch() in the ShowUsage method so you can see if it's getting hit at runtime.
Can you reproduce the issue with a simple exe, only accepting those help parameters?

Categories