I'm really weak in Powershell scripting and hoping someone can put the following pieces together to help me solve the following problem.
Background:
Whenever I update my NuGet packages, they often will touch the app.config and web.config files in my project, thereby, changing the formatting.
This has been reported here without a solution.
Nuget and web.config formatting
http://nuget.codeplex.com/workitem/1511
Goal:
Ultimately, what I'd like to do is have a context menu in Visual Studio (e.g. at the solution level)
that gets a list of all app.config and web.config
and apply the CTRL+K+D to format the config files
(where we have it set to have one attribute per line)
Basically, I'm looking for a modified script that I can hook up to Visual Studio context menu (at solution level) that will read only the relevant config files. OR, alternatively, executed on file save (for relevant config files).
THANKS!
My starting point is from
a) Mark Melville showing how to execute a powershell script from within the context menu in Visual Studio
How do I run a PowerShell script from Visual Studio 2010
b) and Scripts from Phil Haack and David Fowl
http://haacked.com/archive/2011/05/22/an-obsessive-compulsive-guide-to-source-code-formatting.aspx/
https://gist.github.com/davidfowl/984358
For reference, cut and pasted from above links,
Adding context menu to Visual Studio
Add an "External Tool". Go to Tools > External Tools. Add a new one with these settings:
Title: Run with Powershell
Command: powershell.exe
Arguments: -ExecutionPolicy RemoteSigned -File "$(ItemPath)"
Initial Directory: $(ItemDir)
Check "Use Output Window"
If you plan to use scripts that require arguments, check "Prompt For Arguments"
Take note of the position your tool is in the list (1,2, etc...) Click OK.
Now go to Tools > Customize, Commands tab, select Context Menu, and
choose "Project and Solution Context Menus | Item".
Click "Add Command..".
Choose Tools category, and choose "External Command X"
where x is the position your tool was in the list.
Click OK.
Move it to the position you want in the menu, and click "Modify Selection"
to give it a friendly name,
add keyboard shortcuts, etc..
Click Close.
Right click your .ps1 file in the solution explorere and enjoy. (NOTE: I also did this for cmd.exe to run .bat files.)
For reference, cut and pasted from above links,
powershell script to format document
# Print all project items
Recurse-Project -Action {param($item) "`"$($item.ProjectItem.Name)`" is a $($item.Type)" }
# Function to format all documents based on https://gist.github.com/984353
function Format-Document {
param(
[parameter(ValueFromPipelineByPropertyName = $true)]
[string[]]$ProjectName
)
Process {
$ProjectName | %{
Recurse-Project -ProjectName $_ -Action { param($item)
if($item.Type -eq 'Folder' -or !$item.Language) {
return
}
$window = $item.ProjectItem.Open('{7651A701-06E5-11D1-8EBD-00A0C90F26EA}')
if ($window) {
Write-Host "Processing `"$($item.ProjectItem.Name)`""
[System.Threading.Thread]::Sleep(100)
$window.Activate()
$Item.ProjectItem.Document.DTE.ExecuteCommand('Edit.FormatDocument')
$Item.ProjectItem.Document.DTE.ExecuteCommand('Edit.RemoveAndSort')
$window.Close(1)
}
}
}
}
}
function Recurse-Project {
param(
[parameter(ValueFromPipelineByPropertyName = $true)]
[string[]]$ProjectName,
[parameter(Mandatory = $true)]$Action
)
Process {
# Convert project item guid into friendly name
function Get-Type($kind) {
switch($kind) {
'{6BB5F8EE-4483-11D3-8BCF-00C04F8EC28C}' { 'File' }
'{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}' { 'Folder' }
default { $kind }
}
}
# Convert language guid to friendly name
function Get-Language($item) {
if(!$item.FileCodeModel) {
return $null
}
$kind = $item.FileCodeModel.Language
switch($kind) {
'{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}' { 'C#' }
'{B5E9BD33-6D3E-4B5D-925E-8A43B79820B4}' { 'VB' }
default { $kind }
}
}
# Walk over all project items running the action on each
function Recurse-ProjectItems($projectItems, $action) {
$projectItems | %{
$obj = New-Object PSObject -Property #{
ProjectItem = $_
Type = Get-Type $_.Kind
Language = Get-Language $_
}
& $action $obj
if($_.ProjectItems) {
Recurse-ProjectItems $_.ProjectItems $action
}
}
}
if($ProjectName) {
$p = Get-Project $ProjectName
}
else {
$p = Get-Project
}
$p | %{ Recurse-ProjectItems $_.ProjectItems $Action }
}
}
# Statement completion for project names
Register-TabExpansion 'Recurse-Project' #{
ProjectName = { Get-Project -All | Select -ExpandProperty Name }
}
Related
Anyone know how to obtain the current DSC working folder?
On the server, when DSC runs, it generates a folder stucture like the following:
C:\Packages\Plugins\Microsoft.Powershell.DSC\2.18.0.0\DSCWork\DSC.0
The only trouble is when DSC gets a new version, it increments the ".X" folder.. However, when DSC runs, there is no clear way how to resolve the current folder from a script block predictably:
Script GetDscCurrentPath
{
TestScript = {
return $false
}
SetScript ={
Write-Verbose (Get-Item 'C:\%path_to_dsc%\FileInsideMyDscAsset.txt')
}
GetScript = { #{Result = "GetDscCurrentPath"} }
}
Thoughts? Thanks in advance!!!
The DSC Extension does not expose a variable or function to get the current working directory, but since your script / module is in the directory you should be able to use the PSScriptRoot to get the directory. Here is an example:
Write-Verbose -Message "PSScriptRoot: $PSScriptRoot" -Verbose
# Note PSScriptRoot will change when the extension executes
# your configuration function. So, you need to save the value
# when your configuration is loaded
$DscWorkingFolder = $PSScriptRoot
configuration foo {
Script GetDscCurrentPath
{
TestScript = {
return $false
}
SetScript ={
Write-Verbose $using:DscWorkingFolder
}
GetScript = { #{Result = $using:DscWorkingFolder} }
}
}
I'm writing a PowerShell script that uses the reflection APIs to get all the namespaces in an assembly. Anyways, that's not relevant. Here is the relevant portion of the code:
function Get-Namespaces($assembly)
{
$assemblyClass = [Reflection.Assembly]
$assemblyObject = $assemblyClass::ReflectionOnlyLoadFrom($assembly)
$types = $assemblyObject.GetTypes() # This is the part that's having issues
return $types | ? IsPublic | select Namespace -Unique
}
cd $PSScriptRoot
$assemblies = #()
$assemblies += Get-WpfAssemblies
$assemblies += Get-UwpAssembly
$namespaces = $assemblies | % {
% { Get-Namespaces $_ }
}
For some reason, the part that initializes $types seems to be having issues; specifically, it's telling me to catch the exception and check the LoaderExceptions property of the caught exception for more information. So when I try to do just that:
try { $assemblyObject.GetTypes() } catch { echo $_.LoaderExceptions }
and run it, the script prints nothing.
Why does this happen and how can I fix it?
For people who would like to try out the script in its entirety, I've made a publicly available GitHub gist. (Note that it will only work if you have the Windows 10 dev tools installed, but I'm sure reasonably experienced PowerShell users can modify the script to run on their machines.)
Unfortunately, I'm not on a Windows PC to try this, but with some google searching, it looks like the correct syntax should be:
try {
....
} catch [System.Reflection.ReflectionTypeLoadException] {
echo $_.LoaderExceptions
}
Check out http://www.vexasoft.com/blogs/powershell/7255220-powershell-tutorial-try-catch-finally-and-error-handling-in-powershell. Seems to have some good information on exception handling in PowerShell.
The (topmost) exception you're catching is probably an ErrorRecord, which doesn't have a property LoaderExceptions. PowerShell expands missing properties to $null values, which get converted to an empty string for output. You can check the exception type as well as its properties and methods by inspecting the current object in the catch block with the Get-Member cmdlet:
try { $assemblyObject.GetTypes() } catch { Get-Member -InputObject $_ }
Since PowerShell has a tendency of hiding relevant information in nested exceptions you may want to do something like this to unroll them:
try {
...
} catch {
$_.InvocationInfo.Line.Trim() + "`n"
$_.InvocationInfo.PositionMessage + "`n"
$e = $_.Exception
do {
$e.Message
if ($e.LoaderExceptions) { $e.LoaderExceptions }
$e = $e.InnerException
} while ($e)
}
The problem was that PowerShell was interpreting what was echoed as a return value:
function Generate-ErrorMessage
{
try
{
blah
}
catch
{
echo $_
}
}
$message = Generate-ErrorMessage # Will contain some PowerShell message about not being able to find 'blah'
The solution was to use Console.WriteLine directly:
function Generate-ErrorMessage
{
try
{
blah
}
catch
{
[Console]::WriteLine($_)
}
}
Generate-ErrorMessage # prints the message to the console
Not as pretty, but it works as expected.
EDIT: Write-Host also works:
try { blah }
catch { Write-Host $_ }
For other commands, you can take a look here.
EDIT 2: In fact, Out-Host is even better for logging:
try { blah }
catch { $_ | gm | Out-Host } # displays more detailed info than Write-Host
I'm trying to use the Windows.System namespace, part of WinRT, in PowerShell.
Instead of a dll, the assembly comes as a winmd. The two ways of loading assemblies into PowerShell don't seem to work
#[System.Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Windows Kits\8.0\References\CommonConfiguration\Neutral\Windows.winmd")
#Add-Type -Path "C:\Program Files (x86)\Windows Kits\8.0\References\CommonConfiguration\Neutral\Windows.winmd"
I am aware that using Windows API's is a bit trickier than loading my .NET code. I have pinched code for using parts of the win32, would a WinRT solution be similar?
$script:nativeMethods = #();
function Register-NativeMethod([string]$dll, [string]$methodSignature)
{
$script:nativeMethods += [PSCustomObject]#{ Dll = $dll; Signature = $methodSignature; }
}
function Add-NativeMethods()
{
$nativeMethodsCode = $script:nativeMethods | % { "
[DllImport(`"$($_.Dll)`")]
public static extern $($_.Signature);
" }
Add-Type #"
using System;
using System.Runtime.InteropServices;
public static class NativeMethods {
$nativeMethodsCode
}
"#
}
Register-NativeMethod "user32.dll" "int MessageBox (IntPtr hWnd, string text, string caption,int type)"
Add-NativeMethods
[NativeMethods]::MessageBox(0, "Please do not press this again.", "Attention", 0)| Out-Null
My goal is to run Windows.System.Launcher.LaunchFileAsync("C:\file"); in PowerShell. How can I load the WinRT components I need?
To load WinRT binaries into PowerShell use this example:
> [Windows.Data.Json.JsonValue,Windows.Web,ContentType=WindowsRuntime]
> $value = new-object Windows.Data.Json.JsonObject
> $value.Stringify()
{}
I think you will need a StorageFile:
> [Windows.System.Launcher,Windows.Web,ContentType=WindowsRuntime]
> [Windows.System.Launcher]::LaunchFileAsync
OverloadDefinitions
-------------------
static Windows.Foundation.IAsyncOperation[bool] LaunchFileAsync(Windows.Storage.IStorageFile file)
static Windows.Foundation.IAsyncOperation[bool] LaunchFileAsync(Windows.Storage.IStorageFile file, Windows.System.LauncherOptions options)
To create one, you may do something like this:
> [Windows.Management.Core.ApplicationDataManager,Windows.Web,ContentType=WindowsRuntime]
> $value = [Windows.Management.Core.ApplicationDataManager]::CreateForPackageFamily("BackgroundTaskApp_mxmz85hp10cp4")
> $asyncInfo = $value.LocalFolder.CreateFileAsync("foo.txt")
To await *Async() methods, you will need to do something like to create a wrapper.
NuGet package restore does not play well with the PostSharp package, this is of great annoyance to me and manually editing the csproj files is quickly becoming old.
I want to make a small Visual Studio AddIn that fixes the project for me by adding an extra button to the unloaded project context menu.
The problem I am facing sounds simple but haunts me: I somehow can't locate the CommandBar for the unloaded project context menu.
Thus, summarizing the question: What is the CommandBar name for the unloaded project context menu.
Many thanks!
Apparantly: "Stub Project". Found in the list at: CommandBar names
To sum up the OnConnection method to register this:
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
if (connectMode == ext_ConnectMode.ext_cm_UISetup)
{
object[] contextGUIDS = new object[] { };
Commands2 commands = (Commands2)_applicationObject.Commands;
string toolsMenuName = "Tools";
//Place the command on the tools menu.
//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
//Find the Tools command bar on the MenuBar command bar:
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBars cmdBars = (CommandBars)(_applicationObject.CommandBars);
CommandBar vsBarProject = cmdBars["Stub Project"];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
// just make sure you also update the QueryStatus/Exec method to include the new command names.
try
{
//Add a command to the Commands collection:
Command command = commands.AddNamedCommand2(_addInInstance, "FixNuGetPostSharp", "Fix NuGet PostSharp", "Executes the command for VsextensionTest", true, 0, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
//Add a control for the command to the tools menu:
if ((command != null) && (toolsPopup != null))
{
command.AddControl(vsBarProject);
}
}
catch (System.ArgumentException)
{
//If we are here, then the exception is probably because a command with that name
// already exists. If so there is no need to recreate the command and we can
// safely ignore the exception.
}
}
}
We copy a lot of data around in our organisation, and we like using RoboCopyPlus for the robustness and the email reports at the end.
I've added the RoboCopyPlus string to the folder context menu in the registry as:
cmd /c robocopyplus "%1" "C:\Data" *.* /s
But that means I can only copy a folder to C:\Data.
What's the best way to prompt for user input or create a variable that I can pass in to the command? Ideally I would like a folder browser dialogue to pop up and ask them the location, but accepting that that's probably complicating the matter, how would I prompt for user input in the shell?
Write a simple application to have the user select a file, then launch RoboCopyPlus using the path that was selected. Add an entry to your context menu that launches this application instead. Here's an example in C# using the FolderBrowserDialog class and Process.Start().
using System;
using System.Diagnostics;
using System.Windows.Forms;
namespace RobocopyLauncher
{
class Launcher
{
static void Main(string[] args)
{
FolderBrowserDialog browser = new FolderBrowserDialog();
if (browser.ShowDialog() == DialogResult.OK && args.Length == 1)
{
// Not sure of the exact command but it would be
// something like this
Process.Start(string.Format("robocopyplus \"{0}\" \"{1}\"",
args[0], browser.SelectedPath);
}
}
}
}