Detect current VSIX package's version from code - c#

I am writing a VSIX project and I would like for the code to be able to determine whether an update is available.
I know Visual Studio would be able to check for the update, however, I would like for the extension to be able to prompt user (developer) more verbosely.
I wonder how could make the extension read its own version from the package manifest?
Thank you.

I found that I could read the version information directly from the manifest XML file.
var doc = new XmlDocument();
doc.Load(manifestPath);
var metaData = doc.DocumentElement.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Metadata");
var identity = metaData.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Identity");
var version = identity.GetAttribute("Version");
I also wrote a gist C# class code that encapsulate the code above. Besides, version, this technique could be used to get other information provided by the manifest file.

Nordin's solution seems good, but I just want to mention that there is one more way to get current version of the extension. I have no idea in what situation my solution might be better, maybe if you don't know the path to the manifest on the client that uses this extension.
// get ExtensionManager
IVsExtensionManager manager = GetService(typeof(SVsExtensionManager)) as IVsExtensionManager;
// get your extension by Product Id
IInstalledExtension myExtension = manager.GetInstalledExtension("ProductId-1234-1234-1234-123456789012");
// get current version
Version currentVersion = myExtension.Header.Version;
I call this inside Initialize() method of my package.

This code motivated by Wayne Koorts works for me, even if deploying the package on Visual Studio Gallery:
private string getVsixVersion()
{
var asm = Assembly.GetExecutingAssembly();
var asmDir = Path.GetDirectoryName(asm.Location);
var manifestPath = Path.Combine(asmDir, "extension.vsixmanifest");
var version = "?";
if (File.Exists(manifestPath))
{
var doc = new XmlDocument();
doc.Load(manifestPath);
var metaData = doc.DocumentElement.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Metadata");
var identity = metaData.ChildNodes.Cast<XmlElement>().First(x => x.Name == "Identity");
version = identity.GetAttribute("Version");
}
return version;
}

Related

Api for working with a classes as OOP?

I'm writing a 3rd party app that needs to read in .cs files and be able to manipulate classes, then ultimately save back to file.
The type of code I am looking at would be something like:
var classManager = new classManager();
var classes = classManager.LoadFromFile(filePath);
var class = classes[0]; // Just illustrating more than 1 class can exist in a file
var prop = new ClassProperty {Type=MyType.GetType() };
prop.AddGet("return x+y < 50");
//stuff like prop.ReadOnly = true;
class.AddProperty(prop);
var method = new ClassMethod {signature="int id, string name"};
method.MethodBody = GetMethodBodyAsString(); //not writing out an entire method body here
class.AddMethod(method);
class.SaveToFile(true); //Format code
Does such a library exist?
The .NET Compiler Platform Roslyn is what you're looking for. It supports parsing and editting cs files. Check out this post for an example

Modify programatically csproj files with Microsoft.Build.Evaluation (instead of Engine)

I would like to read, modify and write back csproj files.
I've found this code, but unfortunately Engine class is depreciated.
Engine engine = new Engine()
Project project = new Project(engine);
project.Load("myproject.csproj");
project.SetProperty("SignAssembly", "true");
project.Save("myproject.csproj");
So I've continued based on the hint I should use Evaluation.ProjectCollection instead of Engine:
var collection = new ProjectCollection();
collection.DefaultToolsVersion = "4.0";
var project = new Project(collection);
// project.Load("myproject.csproj") There is NO Load method :-(
project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// ... modify the project
project.Save(); // Interestingly there is a Save() method
There is no Load method anymore. I've tried to set the property FullPath, but the project still seems empty. Missed I something?
(Please note I do know that the .csproj file is a standard XML file with XSD schema and I know that we could read/write it by using XDocument or XmlDocument. That's a backup plan. Just seeing the .Save() method on the Project class I think I missed something if I can not load an existing .csproj. thx)
I've actually found the answer, hopefully will help others:
Instead of creating a new Project(...) and trying to .Load(...) it, we should use a factory method of the ProjectCollection class.
// Instead of:
// var project = new Project(collection);
// project.FullPath = "myproject.csproj"; // Instead of load? Does nothing...
// use this:
var project = collection.LoadProject("myproject.csproj")
Since i can't comment:
This won't work in .net core without first setting the MSBuild.exe path variable. The code to do so can be found here
https://blog.rsuter.com/missing-sdk-when-using-the-microsoft-build-package-in-net-core/
and is written here
private static void SetMsBuildExePath()
{
try
{
var startInfo = new ProcessStartInfo("dotnet", "--list-sdks")
{
RedirectStandardOutput = true
};
var process = Process.Start(startInfo);
process.WaitForExit(1000);
var output = process.StandardOutput.ReadToEnd();
var sdkPaths = Regex.Matches(output, "([0-9]+.[0-9]+.[0-9]+) \\[(.*)\\]")
.OfType<Match>()
.Select(m => System.IO.Path.Combine(m.Groups[2].Value, m.Groups[1].Value, "MSBuild.dll"));
var sdkPath = sdkPaths.Last();
Environment.SetEnvironmentVariable("MSBUILD_EXE_PATH", sdkPath);
}
catch (Exception exception)
{
Console.Write("Could not set MSBUILD_EXE_PATH: " + exception);
}
}

Upgraded dotLess error: IImporter does not contain definition for 'Paths'

I have the following code in a project I try to upgrade the "dotless" NuGet package from the "1.2.2.0" to the latest (at moment "1.4.0.0"):
private void GetStylesheetContent(HttpContext context, string name)
{
var conf = BundleConfigSectionHandler.GetConfig();
var elt = conf.Stylesheets.GetBundle(name);
if (elt != null) {
Minifier minifier = null;
if (_conf.Stylesheets.Minify) {
minifier = new Minifier();
}
var files = elt.ListFiles();
var existingFiles = new List<string>();
StringBuilder buffer = new StringBuilder();
foreach (var file in files) {
var physicalFile = context.Request.MapPath(file);
if (File.Exists(physicalFile)) {
existingFiles.Add(physicalFile);
string content;
var path = VirtualPathUtility.GetDirectory(file);
if (file.EndsWith(".less", StringComparison.OrdinalIgnoreCase))
{
var reader = new dotless.Core.Input.VirtualFileReader();
var localpath = VirtualPathUtility.ToAbsolute(file);
content = reader.GetFileContents(localpath);
var parse = new Parser();
parse.Importer = new Importer(reader);
/*Error>*/ parse.Importer.Paths.Add(VirtualPathUtility.ToAbsolute(path));
var eng = new LessEngine(parse);
content = eng.TransformToCss(content, localpath);
The error is on the third line from bottom. It says:
Error 417 'dotless.Core.Importers.IImporter' does not contain a
definition for 'Paths' and no extension method 'Paths' accepting a
first argument of type 'dotless.Core.Importers.IImporter' could be
found (are you missing a using directive or an assembly reference?)
Is a pity that the team didn't left the old method with an [Obsolete] attribute and suggestion to upgrade.
Does anyone know how to replace the "Importer.Paths.Add" method ?
I'm not that familiar with the inner workings of dotless. But looking at the source code for the Importer. paths has been protected since version version 1.2.3. Looking around at the class a little more, it seems like you need to use an instance of dotless.Parser.Tree.Import to add your paths manually.
It does look like this is pretty far off the normal dotless path. So it wouldn't surprise me if the API is a little unstable in these areas. You also may want to look at how bundling works in a question like How to use ASP.Net MVC 4 to Bundle LESS files in Release mode? to see how they handle all the dotless classes.

How to get Workspace object in new TeamFoundation 2013 Templates

In new version of TeamFoundation 2013 default build templates, the Workspace variable is missing. It is needed as intput parameter for few key activities like ConvertWorkspaceItem. How do I get current workspace for TfvcTemplate.12.xaml templates? I've tried to use this msdn thread but it's not working for me (returns null workspace name). Any suggestions?
There's a new activity in 2013 called GetLocalPath that replaces ConvertWorkspaceItem.
The activity is under the Microsoft.TeamFoundation.Build.Activities.Core namespace in the Microsoft.TeamFoundation.Build.Activities assembly.
It uses the LocalPathProvider class that aggregates all workspaces used in the build and exposes path translation for all of them in one place. This basically removes the dependency of knowing the workspace in order to translate server paths to local paths and allows you to use as many workspaces as you want without worrying about breaking something down the line.
When MS takes something away, it's usually for a good reason. "hacking" is really not necessary.
I went with a hack using internal classes from Microsoft.TeamFoundation.Build.Activities.dll (used by microsoft to create workspace name). You need to create custom activity with following code:
public sealed class GetDefaultWorkspace : BaseActivity<Workspace>
{
public override Activity CreateBody()
{
var type = typeof(TfGetSources).Assembly.GetType("Microsoft.TeamFoundation.Build.Activities.TeamFoundation.TfGetSources+GetDefaultWorkspaceName");
var activity = (CodeActivity<string>)Activator.CreateInstance(type);
var sequence = new Sequence();
var workspaceName = new Variable<string>();
sequence.Variables.Add(workspaceName);
sequence.Activities.Add(activity);
activity.Result = (OutArgument<string>) workspaceName;
sequence.Activities.Add(new GetWorkspace
{
Name = workspaceName,
Result = new LambdaReference<Workspace>(ctx => Result.Get(ctx))
});
return sequence;
}
}
This answer might work better for some people. ghord's answer works well, and passes the Workspace back where it can be used in the XAML. However, for my purposes I only want the workspace in my custom TFS activities, so I ended up with this alternative...
public sealed class CustomActivity : CodeActivity
{
protected override void Execute(CodeActivityContext context)
{
// get workspace
var buildDetail = context.GetExtension<IBuildDetail>();
var buildAgent = context.GetExtension<IBuildAgent>();
var buildDirectory = buildAgent.GetExpandedBuildDirectory(buildDetail.BuildDefinition);
var workspacePath = Path.Combine(buildDirectory, "src");
var wsInfo = Workstation.Current.GetLocalWorkspaceInfo(workspacePath);
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(wsInfo.ServerUri);
tfs.Connect(ConnectOptions.None);
var vcs = tfs.GetService<VersionControlServer>();
// finally can get to the workspace here
var workspace = vcs.GetWorkspace(workspacePath);
}
}
Using this method, I don't have to have an activity that just returns the workspace, and then have to pass the workspace into other TFS activities. I just get the workspace from within my own activity while it runs.
I believe the method employed here will use the already downloaded workspace. Keep in mind, that this approach will only work within the scope of "Run on agent" sequence after "Initialize Environment" and before ResetEnvironment within the finally statement of Try Compile, Test, Publish. Else, the workflow will have no knowledge of a sources directory.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/420ba073-bdf5-4ab4-88da-c84561d1a1ba/creating-dynamic-working-folder-in-tfs2013-defaulttemplate?forum=tfsbuild

How can I programmatically checkin code in TFS with policy override?

I'm trying to checkin code from a c# program in a part of TFS repository which will trigger a gated checkin build and also a continuous integration build.
This is my code so far:
public static void Checkin(string path, string comment)
{
var wsInfo = Workstation.Current.GetLocalWorkspaceInfo(path);
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(wsInfo.ServerUri);
tfs.Connect(ConnectOptions.None);
var vcs = tfs.GetService<VersionControlServer>();
var ws = vcs.GetWorkspace(path);
var fullPath = Path.GetFullPath(path);
var change = ws.GetPendingChangesEnumerable().Where(p => p.LocalItem == fullPath).ToArray();
ws.CheckIn(change.ToArray(), comment);
tfs.Dispose();
}
what is basically happening is that I get a GatedCheckinException say that there is an affected build defintion.
I would like to do the checkin with the commonly known bypass to avoid triggering the gated checkin. I've been struggling with the altenate Checkin functions without success.
Any idea?
UPDATE:
Thanks Aghilas Yakoub for the link.
For completeness, here is the code to do the checkin with override that worked for me:
var wip = new WorkspaceCheckInParameters(change, comment)
{
OverrideGatedCheckIn = ((CheckInOptions2)vcs.SupportedFeatures & CheckInOptions2.OverrideGatedCheckIn) == CheckInOptions2.OverrideGatedCheckIn,
PolicyOverride = new PolicyOverrideInfo("Check-in from the build.", null)
};
ws.CheckIn(wip);
I suggest you this sample of code http://blogs.infosupport.com/override-gated-check-in-using-the-tfs-api/

Categories

Resources