How to automate Package Manager Console in Visual Studio 2013 - visual-studio

My specific problem is how can I automate "add-migration" in a build process for the Entity Framework. In researching this, it seems the mostly likely approach is something along the lines of automating these steps
Open a solution in Visual Studio 2013
Execute "Add-Migration blahblah" in the Package Manager Console (most likely via an add-in vsextention)
Close the solution
This initial approach is based on my own research and this question, the powershell script ultimately behind Add-Migration requires quite a bit of set-up to run. Visual Studio performs that setup automatically when creating the Package Manager Console and making the DTE object available. I would prefer not to attempt to duplicate that setup outside of Visual Studio.
One possible path to a solution is this unanswered stack overflow question
In researching the NuGet API, it does not appear to have a "send this text and it will be run like it was typed in the console". I am not clear on the lines between Visual Studio vs NuGet so I am not sure this is something that would be there.
I am able to find the "Pacakage Manager Console" ironically enough via "$dte.Windows" command in the Package Manager Console but in a VS 2013 window, that collection gives me objects which are "Microsoft.VisualStudio.Platform.WindowManagement.DTE.WindowBase". If there is a way stuff text into it, I think I need to get it to be a NuGetConsole.Implementation.PowerConsoleToolWindow" through reviewing the source code I am not clear how the text would stuffed but I am not at all familiar with what I am seeing.
Worst case, I will fall back to trying to stuff keys to it along the lines of this question but would prefer not to since that will substantially complicate the automation surrounding the build process.
All of that being said,
Is it possible to stream commands via code to the Package Manager Console in Visual Studio which is fully initialized and able to support an Entity Framework "add-migration" command?
Thanks for any suggestions, advice, help, non-abuse in advance,
John

The approach that worked for me was to trace into the entity framework code starting in with the AddMigrationCommand.cs in the EntityFramework.Powershell project and find the hooks into the EntityFramework project and then make those hooks work so there is no Powershell dependency.
You can get something like...
public static void RunIt(EnvDTE.Project project, Type dbContext, Assembly migrationAssembly, string migrationDirectory,
string migrationsNamespace, string contextKey, string migrationName)
{
DbMigrationsConfiguration migrationsConfiguration = new DbMigrationsConfiguration();
migrationsConfiguration.AutomaticMigrationDataLossAllowed = false;
migrationsConfiguration.AutomaticMigrationsEnabled = false;
migrationsConfiguration.CodeGenerator = new CSharpMigrationCodeGenerator(); //same as default
migrationsConfiguration.ContextType = dbContext; //data
migrationsConfiguration.ContextKey = contextKey;
migrationsConfiguration.MigrationsAssembly = migrationAssembly;
migrationsConfiguration.MigrationsDirectory = migrationDirectory;
migrationsConfiguration.MigrationsNamespace = migrationsNamespace;
System.Data.Entity.Infrastructure.DbConnectionInfo dbi = new System.Data.Entity.Infrastructure.DbConnectionInfo("DataContext");
migrationsConfiguration.TargetDatabase = dbi;
MigrationScaffolder ms = new MigrationScaffolder(migrationsConfiguration);
ScaffoldedMigration sf = ms.Scaffold(migrationName, false);
}
You can use this question to get to the dte object and from there to find the project object to pass into the call.

This is an update to John's answer whom I have to thank for the "hard part", but here is a complete example which creates a migration and adds that migration to the supplied project (project must be built before) the same way as Add-Migration InitialBase -IgnoreChanges would:
public void ScaffoldedMigration(EnvDTE.Project project)
{
var migrationsNamespace = project.Properties.Cast<Property>()
.First(p => p.Name == "RootNamespace").Value.ToString() + ".Migrations";
var assemblyName = project.Properties.Cast<Property>()
.First(p => p.Name == "AssemblyName").Value.ToString();
var rootPath = Path.GetDirectoryName(project.FullName);
var assemblyPath = Path.Combine(rootPath, "bin", assemblyName + ".dll");
var migrationAssembly = Assembly.Load(File.ReadAllBytes(assemblyPath));
Type dbContext = null;
foreach(var type in migrationAssembly.GetTypes())
{
if(type.IsSubclassOf(typeof(DbContext)))
{
dbContext = type;
break;
}
}
var migrationsConfiguration = new DbMigrationsConfiguration()
{
AutomaticMigrationDataLossAllowed = false,
AutomaticMigrationsEnabled = false,
CodeGenerator = new CSharpMigrationCodeGenerator(),
ContextType = dbContext,
ContextKey = migrationsNamespace + ".Configuration",
MigrationsAssembly = migrationAssembly,
MigrationsDirectory = "Migrations",
MigrationsNamespace = migrationsNamespace
};
var dbi = new System.Data.Entity.Infrastructure
.DbConnectionInfo("ConnectionString", "System.Data.SqlClient");
migrationsConfiguration.TargetDatabase = dbi;
var scaffolder = new MigrationScaffolder(migrationsConfiguration);
ScaffoldedMigration migration = scaffolder.Scaffold("InitialBase", true);
var migrationFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".cs");
File.WriteAllText(migrationFile, migration.UserCode);
var migrationItem = project.ProjectItems.AddFromFile(migrationFile);
var designerFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".Designer.cs");
File.WriteAllText(designerFile, migration.DesignerCode);
var designerItem = project.ProjectItems.AddFromFile(migrationFile);
foreach(Property prop in designerItem.Properties)
{
if (prop.Name == "DependentUpon")
prop.Value = Path.GetFileName(migrationFile);
}
var resxFile = Path.Combine(rootPath, migration.Directory,
migration.MigrationId + ".resx");
using (ResXResourceWriter resx = new ResXResourceWriter(resxFile))
{
foreach (var kvp in migration.Resources)
resx.AddResource(kvp.Key, kvp.Value);
}
var resxItem = project.ProjectItems.AddFromFile(resxFile);
foreach (Property prop in resxItem.Properties)
{
if (prop.Name == "DependentUpon")
prop.Value = Path.GetFileName(migrationFile);
}
}
I execute this in my project template's IWizard implementation where I run a migration with IgnoreChanges, because of shared entites with the base project. Change scaffolder.Scaffold("InitialBase", true) to scaffolder.Scaffold("InitialBase", false) if you want to include the changes.

Related

How to unit test Azure Functions in Visual Studio

I am using Visual Studio to create Azure Functions. I can create, publish and run functions manually. If I set my Function project to Start Up and run, I host starts. How do I get the Host to start when using MSTest ?
I want to write a test using RestSharp and invoke the functions during the tests - the way the actual applicaion will work. It seems I need a way to get MSTest to start the Azure Function Host.
I was hoping to find an approach similar to debugging Asp.Net/SOAP in older versions of VS where the MSTest engine would start the IISExpress and attach VS to the Asp.Net projects. Edit and continue was supported.
I have worked out the following approaches, so far:
RestSharp Code:
var url = $"http://localhost:7071/api";
var functionKey = "this value is ignored by 'Azure Functions Core Tools' ";
var client = new RestSharp.RestClient(url);
var request = new RestSharp.RestRequest("GetConnectionString", RestSharp.Method.POST);
request.AddHeader("x-functions-key", functionKey);
var response = client.Execute<string>(request);
Option 1:
Run 2nd instance of VS and run the Functions.
Update code to reflect url paths displayed by Azure Functions Core Tools.
Edit and continue works.
Option 2:
Add this class to UnitTest project:
Attach to func.exe process to debug functions
Edit and continue do not work.
[TestClass]
public class Initializer
{
static System.Diagnostics.Process process { get; set; }
[AssemblyInitialize]
public static void Initialize(TestContext context)
{
//process does not use the WorkingDirectory properly with %userprofile%
var userprofile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var path = $#"{userprofile}\source\repos\mypro\...\myproj.Functions\bin\Debug\netcoreapp3.1";
var startInfo = new ProcessStartInfo
{
FileName = #"cmd.exe",
Arguments = #"/C ""%localappdata%\AzureFunctionsTools\Releases\3.2.0\cli_x64\func.exe host start --port 7071 --pause-on-error""",
//RedirectStandardInput = true,
//RedirectStandardOutput = true,
//RedirectStandardError = true,
//UseShellExecute = false,
UseShellExecute = true,
WorkingDirectory = path
};
process = new System.Diagnostics.Process()
{
StartInfo = startInfo
};
process.Start();
// Thread.Sleep(3000);
}
[AssemblyCleanup]
public static void Cleanup()
{
process.CloseMainWindow();
}
}
Option 3:
Start func.exe process
Working Folder: ...\source\repos\xxx\xxx\xxx.Functions\bin\Debug\netcoreapp3.1\
Command Line:
"C:\Users\username\AppData\Local\AzureFunctionsTools\Releases\3.2.0\cli_x64\func.exe" host start --port 7071 --pause-on-error
Attach to func.exe process.
Edit and continue does not work.

Programmatically access TFS annotations to determine owner

I'm working on a project team and our application is in TFS. I'm attempting to determine how many lines of code each team member is responsible. In TFS, I'm aware of the Annotate feature in the Visual Studio interface which allows you to see who last modified each line of code so I know TFS has this information.
I've written a small console app which accesses my TFS project and all its files, but I now need to programmatically access annotations so I can see who the owner of each line is. Here is my existing code:
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
public class Program
{
static void Main(string[] args)
{
var credentials = new NetworkCredential(username, password, domain);
var server = new TfsTeamProjectCollection(new Uri(serverUrl), credentials);
var version = server.GetService(typeof(VersionControlServer)) as VersionControlServer;
var items = version.GetItems(projectPath, RecursionType.Full);
var fileItems = items.Items.Where(x => x.ItemType == ItemType.File);
foreach (var fileItem in fileItems)
{
var serverItem = fileItem.ServerItem;
//TODO: retrieve and parse annotations
}
}
}
I can't seem to figure out how to retrieve annotations once I have the TFS item. This link explains how to do it by calling TFPT, but after implementing it (tfpt annotate /noprompt <filename>), you are only give the last changeset and code per line, not the owner.
I also found a Microsoft.TeamFoundation.VersionControl.Server namespace that has an Annotation class. I installed TFS on my machine to have access to that DLL, but it doesn't seem like it is of any help to this problem.
How can you programmatically access TFS annotations to determine the owner of a line of code for a file?
You may have to query the branch when a Item's change type is Branch.
For a simple example, there is a scenario
$/Project
/Main`
/a.txt
/Develop
/a.txt (branched from main)
When you query the history of $/project/Develop/a.txt, you can also get the history of $/project/Main/a.txt using following code
void GetAllHistory(string serverItem)
{
var changesets=vcs.QueryHistory(serverItem,
Microsoft.TeamFoundation.VersionControl.Client.VersionSpec.Latest,
0,
Microsoft.TeamFoundation.VersionControl.Client.RecursionType.None,
null,
new Microsoft.TeamFoundation.VersionControl.Client.ChangesetVersionSpec(1),
Microsoft.TeamFoundation.VersionControl.Client.VersionSpec.Latest,
int.MaxValue,
true,
false);
foreach (var obj in changesets)
{
Microsoft.TeamFoundation.VersionControl.Client.Changeset cs = obj as Microsoft.TeamFoundation.VersionControl.Client.Changeset;
if (cs == null)
{
return;
}
foreach (var change in cs.Changes)
{
if (change.Item.ServerItem != serverItem)
{
return;
}
Console.WriteLine(string.Format("ChangeSetID:{0}\tFile:{1}\tChangeType:{2}", cs.ChangesetId,change.Item.ServerItem, change.ChangeType));
if ((change.ChangeType & Microsoft.TeamFoundation.VersionControl.Client.ChangeType.Branch) == Microsoft.TeamFoundation.VersionControl.Client.ChangeType.Branch)
{
var items=vcs.GetBranchHistory(new Microsoft.TeamFoundation.VersionControl.Client.ItemSpec[]{new Microsoft.TeamFoundation.VersionControl.Client.ItemSpec(serverItem, Microsoft.TeamFoundation.VersionControl.Client.RecursionType.None)},
Microsoft.TeamFoundation.VersionControl.Client.VersionSpec.Latest);
GetAllHistory(items[0][0].Relative.BranchToItem.ServerItem);
}
}
}
}

Can one rename a source file using Roslyn APIs?

I'm trying to write a UnmatchedClassAndFilename diagnostic and code fix using the new Roslyn and Visual Studio API's. The idea is to rename a class or filename in case they aren't equal.
How can I use the Roslyn API to rename a file in Visual Studio? The Workspace class doesn't seem to support this.
Update: Created an issue at CodePlex (https://roslyn.codeplex.com/workitem/258)
No, there is no current support for this in the Workspaces API. It's a common request but I'm not sure we have something explicitly tracking that work, so feel free to file the bug on CodePlex.
I am using Visual Studio 2017 and the Code Refactoring VSIX project template to accomplish this.
Here is my code:
private async Task<Solution> ConvertTypeNameToPascalCaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken)
{
// Produce a PascalCased version of the type declaration's identifier token.
var identifierToken = typeDecl.Identifier;
var newName = identifierToken.Text.ToPascalCase();
// Get the symbol representing the type to be renamed.
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken);
// Produce a new solution that has all references to that type renamed, including the declaration.
var originalSolution = document.Project.Solution;
var optionSet = originalSolution.Workspace.Options;
var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false);
var newDocId = DocumentId.CreateNewId(document.Project.Id);
var newText = await newSolution.GetDocument(document.Id).GetTextAsync(cancellationToken).ConfigureAwait(false);
// rename document by adding a new document with the new name and removing the old document
newSolution = newSolution.AddAdditionalDocument(newDocId, newName + ".cs", newText);
newSolution = newSolution.RemoveDocument(document.Id);
// Return the new solution with the now PascalCased type name.
return newSolution;
}
Note: ToPascalCase() is an extension method that I added to the string class.
The major point to notice is that I used AddAdditionalDocument() and RemoveDocument() to effectively rename the existing document to match my new name.
Here is the code that sets up the Code Refactoring engine:
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
// Find the node at the selection.
var node = root.FindNode(context.Span);
// Only offer a refactoring if the selected node is a type declaration node.
var typeDecl = node as TypeDeclarationSyntax;
if (typeDecl == null)
{
return;
}
if (typeDecl.Identifier.Text.IsUpper())
{
// For any type declaration node, create a code action to reverse the identifier text.
var action = CodeAction.Create("Convert type name to PascalCase", c => ConvertTypeNameToPascalCaseAsync(context.Document, typeDecl, c));
// Register this code action.
context.RegisterRefactoring(action);
}
}
Note: IsUpper() is also an extension method that I added to the string class.
Incidentally, my specific use case is to convert all caps class names with underscores in them to PascalCased class names. Examples:
TEST = Test
TEST_CLASS = TestClass
TEST_A_CLASS = TestAClass

Testing SharePoint List Workflow from Visual Studio 2010

I am trying to create a custom workflow in Visual Studio 2010 for SharePoint 2010 and have run into a problem. I have figured out how to deploy the workflow to the SharePoint site, but executing it results in an error. However, the error message is completely non-descriptive, so I want to find out if there is a way to execute it from Visual Studio so I can see where it fails, and possibly why.
I'm trying to simply create a new subsite based on a given ListItem.Title information.
How is it you go about debugging?
For reference, here is my code
class CreateSubsite : System.Workflow.ComponentModel.Activity
{
protected override System.Workflow.ComponentModel.ActivityExecutionStatus
Execute(System.Workflow.ComponentModel.ActivityExecutionContext executionContext)
{
createSite();
return System.Workflow.ComponentModel.ActivityExecutionStatus.Closed;
}
public void createSite()
{
using (SPSite currentSite = SPContext.Current.Site)
{
using (SPWeb currentWeb = SPContext.Current.Web)
{
SPList currentList = SPContext.Current.List;
SPListItem currentListItem = SPContext.Current.ListItem;
WorkflowContext workflow = new WorkflowContext();
SPSite parentSite = new SPSite(workflow.CurrentWebUrl);
SPWeb newSite = currentSite.AllWebs.Add(
currentListItem.Title.Replace(" ", "_"),
currentListItem.Title,
String.Empty, currentWeb.Language, "CI Template", false, false
);
}
}
}
}
Try to remove Using keyword from your code .You should not dispose your SPSite and SPWeb when you use SPContext because disposing of that object might actually break the workflow as it may still need a reference to that object for later use.
just rewrite your code without use using
public void createSite() {
SPSite currentSite = SPContext.Current.Site
SPWeb currentWeb = SPContext.Current.Web
//.... Rest of your code
Hope that help
Regards.

Updating an Activity in MS CRM via web service?

I've been trying to do this all morning. Anyone have a code snippet (C#) showing how to update an "activity" within CRM via the webservice?
I can CreateReadUpdateDelete with entities, but I'm not sure how to do it with Activities.
Can't find anything on google either...
What are you specifically looking to update? Basically, updating an activity is just like updating any other entity, you just have to use the task entity.
public void CloseTask(CrmService crmsvc, Guid activityid, DateTime start, DateTime end)
{
ColumnSet cols = new ColumnSet();
cols.Attributes = new string[] { "activityid", "statecode" };
task tsk = (task)crmsvc.Retrieve(EntityName.task.ToString(), activityid, cols);
if(tsk.statecode.Value != TaskState.Open)
return;
tsk.actualstart = new CRMDateTime();
tsk.actualstart.value = start.ToString();
tsk.actualend = new CRMDateTime();
tsk.actualend.value = end.ToString();
crmsvc.Update(tsk);
SetStateTaskRequest state = new SetStateTaskRequest();
state.EntityId = activityid;
state.TaskState = TaskState.Completed;
state.TaskStatus = -1; // Let MS CRM decide this property;
SetStateTaskResponse stateSet = (SetStateTaskResponse)crmsvc.Execute(state);
}
Edit: added some sample code. note, I had to modify what I had to strip some proprietary code, so I don't know if this will actually compile. It's close though.
We can also update a Custom Workflow Activity Using Assembly Versioning. Below link gives more information:
http://msdn.microsoft.com/en-us/library/gg328011.aspx

Resources