http://blogs.msdn.com/b/powershell/archive/2010/06/21/powershell-integration-into-visual-studio.aspx details a PSH Visual Studio Extension that allows VS users to edit PowerShell scripts in Visual Studio.  The bits are located here: http://powerguivsx.codeplex.com/

It’s actually not that good – it requires a downrev version of PowerGUI Script Editor (specifically 3.2) and basically wraps the VS IDE around the Script Editor.  I don’t see a way to create a solution (group of multiple related files).  Also,the editor behaviours are still controlled by PowerGUI Script Editor, so there isn’t an innate hotkey for collapsing code blocks.

And, it doesn’t work with VisualStudio 2012.

The bugbear is the extension.vsixmanifest file, which caps out the support VS version to 11.0.  The codeplex.com page does have an alpha version of 1.6.1 that is listed to work with VS 2012, but it says alpha, so I’m a little nervous.

A well-documented hack-around is to rename the PowerGUIVsx.vsix file to a .ZIP, extract and edit the extension.vsixmanifest file, overwrite it in the .ZIP, and rename the .ZIP back to PowerGUIVsx.vsix.  For some reason or another, I decided it was a good idea to automate it, in part to learn about manipulating .ZIP files with PowerShell. 

Ouch.

I see now why there are so many web pages that recommend either calling 7z.exe or other ZIP libraries.  COMObject ZIP manipulation is half-baked, painful, and generally harmful to grey matter.  Specifically, when running this function, you’ll see this pop up:

DeleteFileFromZip

That’s because the PSH COMObject doesn’t respect the vOptions documented on MSDN to forcibly overwrite.  Instead, it displays the “do you want to overwrite” dialog, and then immediately executes the next line, so your response doesn’t matter.

Deleting a file from the ZIP COMObject at least interrupts the script, but I can’t find a way to forcibly delete it.  Thus, the dialog box. 

Click ‘Yes’ to continue.

 

function Update-PowerGUIVsx {
     param (
         [string]$Path = $(Join-Path -Path $HOME -ChildPath 'Downloads'),
         [string]$backupFolder = $null,
         [switch]$noBackup
     );
    
     if (!(Test-Path -Path $Path)) { 
         Write-Warning "-Path $Path not found";
         return;
     }
    
     if ($Path -notmatch '\\PowerGUIVsx\.vsix') { 
         $Path = Join-Path -Path $Path -ChildPath '\PowerGUIVsx.vsix'; 
         Write-Verbose "-Path updated to $Path";
     }
    
     if (!(Test-Path -Path $Path)) { 
         Write-Warning "-Path $Path not found";
         return;
     }
    
     $Path = (Resolve-Path -Path $Path).Path;
    
     $dirName = Split-Path -Parent -Path $Path;
     $baseName = Split-Path -Leaf -Path $Path;
    
     if (!$noBackup) {
         if (!$backupFolder) { $backupFolder = $dirName; }
        
         $backupName = $baseName -replace '\.vsix$', "$(Get-Date -Format '-yyyy-MM-dd_HHmmss').vsix";
         $backupFile = Join-Path -Path $backupFolder -ChildPath $backupName;
         Copy-Item -Path $Path -Destination $backupFile -force;
         if (Test-Path $backupFile) {
             Write-Host "Backup file $backupFile created";
         } else {
             Write-Warning "Backup file $backupFile could not be created";
         }
     }
    
     $zipPath = Join-Path -Path $Env:TEMP -ChildPath "$baseName.zip";
     Write-Verbose "`$zipPath = '$zipPath'";
    
     Copy-Item -Path $Path -Destination $zipPath;
     Write-Verbose "Copied $Path to $zipPath";
    
     $shellApp = New-Object -ComObject Shell.Application

     if (!($zipObject = $shellApp.NameSpace($zipPath))) {
         Write-Warning "Unable to open -Path $Path as ZIP object";
         return;
     }
    
     $destination = $shellApp.NameSpace($env:TEMP);
     $fileName = 'extension.vsixmanifest'

     if (!($manifest = $zipObject.Items() | ? { $_.name -eq $fileName })) {
         Write-Warning "Unable to find $fileName in -Path $Path";
         return;
     }
    
     # overwrite, no progress bar;
     $copyFlags = 0x14;
    
     $destination.CopyHere($manifest, 0x14);
     $manifest.InvokeVerb("Delete");

     if (Test-Path -Path $filePath) {
         Write-Verbose "`$filePath = '$filePath' #2";
     } else {
         Write-Warning "Unable to create $filePath";
         return;
     }

     if (!($fileXml = (Get-Content $filePath) -as [xml])) {
         Write-Warning "Unable to open $fileName in -Path $Path as XML.";
         return;
     }            
    
     $fileXml.Vsix.Identifier.SupportedProducts.VisualStudio.SetAttribute("Version", '11.0');
     $fileXml.Save($filePath);
     $zipObject.CopyHere($filePath, 0x14);
     Start-Sleep -Seconds 1;
     Copy-Item -Path $zipPath -Destination $Path;

     if ($DebugPreference -eq "Continue") {
         Copy-Item -Force -Path $Path -Destination "$Path.zip";
         ii "$path.zip";
     }
}