Source : http://blog.danskingdom.com/powershell-ise-multiline-comment-and-uncomment-done-right-and-other-ise-gui-must-haves/

Ctrl + KComment Selected Lines
Ctrl + Shift + KUncomment Selected Lines
  1
  2# Define our constant variables.
  3[string]$NEW_LINE_STRING = "`r`n"
  4[string]$COMMENT_STRING = "#"
  5
  6function Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers([bool]$DoNothingWhenNotCertainOfWhichLinesToSelect = $false)
  7{
  8<#
  9    .SYNOPSIS
 10    Exands the selected text to make sure the entire lines are selected.
 11    Returns $null if we can't determine with certainty which lines to select and the
 12
 13    .DESCRIPTION
 14    Exands the selected text to make sure the entire lines are selected.
 15
 16    .PARAMETER DoNothingWhenNotCertainOfWhichLinesToSelect
 17    Under the following edge case we can't determine for sure which lines in the file are selected.
 18    If this switch is not provided and the edge case is encountered, we will guess and attempt to select the entire selected lines, but we may guess wrong and select the lines above/below the selected lines.
 19    If this switch is provided and the edge case is encountered, no lines will be selected.
 20
 21    Edge Case:
 22    - When the selected text occurs multiple times in the document, directly above or below the selected text.
 23
 24    Example:
 25    abc
 26    abc
 27    abc
 28
 29    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
 30    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
 31    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get selected, so it shouldn't be a big deal.
 32    But if it bugs you, you can provide this switch.
 33
 34    .OUTPUT
 35    PSObject. Returns a PSObject with the properties FirstLineNumber and LastLineNumber, which correspond to the first and last line numbers of the selected text.
 36#>
 37
 38    # Backup all of the original info before we modify it.
 39    [int]$originalCaretLine = $psISE.CurrentFile.Editor.CaretLine
 40    [string]$originalSelectedText = $psISE.CurrentFile.Editor.SelectedText
 41    [string]$originalCaretLineText = $psISE.CurrentFile.Editor.CaretLineText
 42
 43    # Assume only one line is selected.
 44    [int]$textToSelectFirstLine = $originalCaretLine
 45    [int]$textToSelectLastLine = $originalCaretLine
 46
 47    #------------------------
 48    # Before we process the selected text, we need to make sure all selected lines are fully selected (i.e. the entire line is selected).
 49    #------------------------
 50
 51    # If no text is selected, OR only part of one line is selected (and it doesn't include the start of the line), select the entire line that the caret is currently on.
 52    if (($psISE.CurrentFile.Editor.SelectedText.Length -le 0) -or !$psISE.CurrentFile.Editor.SelectedText.Contains($NEW_LINE_STRING))
 53    {
 54        $psISE.CurrentFile.Editor.SelectCaretLine()
 55    }
 56    # Else the first part of one line (or the entire line), or multiple lines are selected.
 57    else
 58    {
 59        # Get the number of lines in the originally selected text.
 60        [string[]] $originalSelectedTextArray = $originalSelectedText.Split([string[]]$NEW_LINE_STRING, [StringSplitOptions]::None)
 61        [int]$numberOfLinesInSelectedText = $originalSelectedTextArray.Length
 62
 63        # If only one line is selected, make sure it is fully selected.
 64        if ($numberOfLinesInSelectedText -le 1)
 65        {
 66            $psISE.CurrentFile.Editor.SelectCaretLine()
 67        }
 68        # Else there are multiple lines selected, so make sure the first character of the top line is selected (so that we put the comment character at the start of the top line, not in the middle).
 69        # The first character of the bottom line will always be selected when multiple lines are selected, so we don't have to worry about making sure it is selected; only the top line.
 70        else
 71        {
 72            # Determine if the caret is on the first or last line of the selected text.
 73            [bool]$isCaretOnFirstLineOfSelectedText = $false
 74            [string]$firstLineOfOriginalSelectedText = $originalSelectedTextArray[0]
 75            [string]$lastLineOfOriginalSelectedText = $originalSelectedTextArray[$originalSelectedTextArray.Length - 1]
 76
 77            # If the caret is definitely on the first line.
 78            if ($originalCaretLineText.EndsWith($firstLineOfOriginalSelectedText) -and !$originalCaretLineText.StartsWith($lastLineOfOriginalSelectedText))
 79            {
 80                $isCaretOnFirstLineOfSelectedText = $true
 81            }
 82            # Else if the caret is definitely on the last line.
 83            elseif ($originalCaretLineText.StartsWith($lastLineOfOriginalSelectedText) -and !$originalCaretLineText.EndsWith($firstLineOfOriginalSelectedText))
 84            {
 85                $isCaretOnFirstLineOfSelectedText = $false
 86            }
 87            # Else we need to do further analysis to determine if the caret is on the first or last line of the selected text.
 88            else
 89            {
 90                [int]$numberOfLinesInFile = $psISE.CurrentFile.Editor.LineCount
 91
 92                [string]$caretOnFirstLineText = [string]::Empty
 93                [int]$caretOnFirstLineArrayStartIndex = ($originalCaretLine - 1) # -1 because array starts at 0 and file lines start at 1.
 94                [int]$caretOnFirstLineArrayStopIndex = $caretOnFirstLineArrayStartIndex + ($numberOfLinesInSelectedText - 1) # -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
 95
 96                [string]$caretOnLastLineText = [string]::Empty
 97                [int]$caretOnLastLineArrayStopIndex = ($originalCaretLine - 1)  # -1 because array starts at 0 and file lines start at 1.
 98                [int]$caretOnLastLineArrayStartIndex = $caretOnLastLineArrayStopIndex - ($numberOfLinesInSelectedText - 1) # -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
 99
100                # If the caret being on the first line would cause us to go "off the file", then we know the caret is on the last line.
101                if (($caretOnFirstLineArrayStartIndex -lt 0) -or ($caretOnFirstLineArrayStopIndex -ge $numberOfLinesInFile))
102                {
103                    $isCaretOnFirstLineOfSelectedText = $false
104                }
105                # If the caret being on the last line would cause us to go "off the file", then we know the caret is on the first line.
106                elseif (($caretOnLastLineArrayStartIndex -lt 0) -or ($caretOnLastLineArrayStopIndex -ge $numberOfLinesInFile))
107                {
108                    $isCaretOnFirstLineOfSelectedText = $true
109                }
110                # Else we still don't know where the caret is.
111                else
112                {
113                    [string[]]$filesTextArray = $psISE.CurrentFile.Editor.Text.Split([string[]]$NEW_LINE_STRING, [StringSplitOptions]::None)
114
115                    # Get the text of the lines where the caret is on the first line of the selected text.
116                    [string[]]$caretOnFirstLineTextArray = @([string]::Empty) * $numberOfLinesInSelectedText # Declare an array with the number of elements required.
117                    [System.Array]::Copy($filesTextArray, $caretOnFirstLineArrayStartIndex, $caretOnFirstLineTextArray, 0, $numberOfLinesInSelectedText)
118                    $caretOnFirstLineText = $caretOnFirstLineTextArray -join $NEW_LINE_STRING
119
120                    # Get the text of the lines where the caret is on the last line of the selected text.
121                    [string[]]$caretOnLastLineTextArray = @([string]::Empty) * $numberOfLinesInSelectedText # Declare an array with the number of elements required.
122                    [System.Array]::Copy($filesTextArray, $caretOnLastLineArrayStartIndex, $caretOnLastLineTextArray, 0, $numberOfLinesInSelectedText)
123                    $caretOnLastLineText = $caretOnLastLineTextArray -join $NEW_LINE_STRING
124
125                    [bool]$caretOnFirstLineTextContainsOriginalSelectedText = $caretOnFirstLineText.Contains($originalSelectedText)
126                    [bool]$caretOnLastLineTextContainsOriginalSelectedText = $caretOnLastLineText.Contains($originalSelectedText)
127
128                    # If the selected text is only within the text of when the caret is on the first line, then we know for sure the caret is on the first line.
129                    if ($caretOnFirstLineTextContainsOriginalSelectedText -and !$caretOnLastLineTextContainsOriginalSelectedText)
130                    {
131                        $isCaretOnFirstLineOfSelectedText = $true
132                    }
133                    # Else if the selected text is only within the text of when the caret is on the last line, then we know for sure the caret is on the last line.
134                    elseif ($caretOnLastLineTextContainsOriginalSelectedText -and !$caretOnFirstLineTextContainsOriginalSelectedText)
135                    {
136                        $isCaretOnFirstLineOfSelectedText = $false
137                    }
138                    # Else if the selected text is in both sets of text, then we don't know for sure if the caret is on the first or last line.
139                    elseif ($caretOnFirstLineTextContainsOriginalSelectedText -and $caretOnLastLineTextContainsOriginalSelectedText)
140                    {
141                        # If we shouldn't do anything since we might comment out text that is not selected by the user, just exit this function and return null.
142                        if ($DoNothingWhenNotCertainOfWhichLinesToSelect)
143                        {
144                            return $null
145                        }
146                    }
147                    # Else something went wrong and there is a flaw in this logic, since the selected text should be in one of our two strings, so let's just guess!
148                    else
149                    {
150                        Write-Error "WHAT HAPPENED?!?! This line should never be reached. There is a flaw in our logic!"
151                        return $null
152                    }
153                }
154            }
155
156            # Assume the caret is on the first line of the selected text, so we want to select text from the caret's line downward.
157            $textToSelectFirstLine = $originalCaretLine
158            $textToSelectLastLine = $originalCaretLine + ($numberOfLinesInSelectedText - 1) # -1 because the starting line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
159
160            # If the caret is actually on the last line of the selected text, we want to select text from the caret's line upward.
161            if (!$isCaretOnFirstLineOfSelectedText)
162            {
163                $textToSelectFirstLine = $originalCaretLine - ($numberOfLinesInSelectedText - 1) # -1 because the stopping line is inclusive (i.e. if we want 1 line the start and stop lines should be the same).
164                $textToSelectLastLine = $originalCaretLine
165            }
166
167            # Re-select the text, making sure the entire first and last lines are selected. +1 on EndLineWidth because column starts at 1, not 0.
168            $psISE.CurrentFile.Editor.Select($textToSelectFirstLine, 1, $textToSelectLastLine, $psISE.CurrentFile.Editor.GetLineLength($textToSelectLastLine) + 1)
169        }
170    }
171
172    # Return the first and last line numbers selected.
173    $selectedTextFirstAndLastLineNumbers = New-Object PSObject -Property @{
174        FirstLineNumber = $textToSelectFirstLine
175        LastLineNumber = $textToSelectLastLine
176    }
177    return $selectedTextFirstAndLastLineNumbers
178}
179
180function CommentOrUncommentIseSelectedLines([bool]$CommentLines = $false, [bool]$DoNothingWhenNotCertainOfWhichLinesToSelect = $false)
181{
182    $selectedTextFirstAndLastLineNumbers = Select-EntireLinesInIseSelectedTextAndReturnFirstAndLastSelectedLineNumbers $DoNothingWhenNotCertainOfWhichLinesToSelect
183
184    # If we couldn't determine which lines to select, just exit without changing anything.
185    if ($selectedTextFirstAndLastLineNumbers -eq $null) { return }
186
187    # Get the text lines selected.
188    [int]$selectedTextFirstLineNumber = $selectedTextFirstAndLastLineNumbers.FirstLineNumber
189    [int]$selectedTextLastLineNumber = $selectedTextFirstAndLastLineNumbers.LastLineNumber
190
191    # Get the Selected Text and convert it into an array of strings so we can easily process each line.
192    [string]$selectedText = $psISE.CurrentFile.Editor.SelectedText
193    [string[]] $selectedTextArray = $selectedText.Split([string[]]$NEW_LINE_STRING, [StringSplitOptions]::None)
194
195    # Process each line of the Selected Text, and save the modified lines into a text array.
196    [string[]]$newSelectedTextArray = @()
197    $selectedTextArray | foreach {
198        # If the line is not blank, add a comment character to the start of it.
199        [string]$lineText = $_
200        if ([string]::IsNullOrWhiteSpace($lineText)) { $newSelectedTextArray += $lineText }
201        else
202        {
203            # If we should be commenting the lines out, add a comment character to the start of the line.
204            if ($CommentLines)
205            { $newSelectedTextArray += "$COMMENT_STRING$lineText" }
206            # Else we should be uncommenting, so remove a comment character from the start of the line if it exists.
207            else
208            {
209                # If the line begins with a comment, remove one (and only one) comment character.
210                if ($lineText.StartsWith($COMMENT_STRING))
211                {
212                    $lineText = $lineText.Substring($COMMENT_STRING.Length)
213                }
214                $newSelectedTextArray += $lineText
215            }
216        }
217    }
218
219    # Join the text array back together to get the new Selected Text string.
220    [string]$newSelectedText = $newSelectedTextArray -join $NEW_LINE_STRING
221
222    # Overwrite the currently Selected Text with the new Selected Text.
223    $psISE.CurrentFile.Editor.InsertText($newSelectedText)
224
225    # Fully select all of the lines that were modified. +1 on End Line's Width because column starts at 1, not 0.
226    $psISE.CurrentFile.Editor.Select($selectedTextFirstLineNumber, 1, $selectedTextLastLineNumber, $psISE.CurrentFile.Editor.GetLineLength($selectedTextLastLineNumber) + 1)
227}
228
229function Comment-IseSelectedLines([switch]$DoNothingWhenNotCertainOfWhichLinesToComment)
230{
231<#
232    .SYNOPSIS
233    Places a comment character at the start of each line of the selected text in the current PS ISE file.
234    If no text is selected, it will comment out the line that the caret is on.
235
236    .DESCRIPTION
237    Places a comment character at the start of each line of the selected text in the current PS ISE file.
238    If no text is selected, it will comment out the line that the caret is on.
239
240    .PARAMETER DoNothingWhenNotCertainOfWhichLinesToComment
241    Under the following edge case we can't determine for sure which lines in the file are selected.
242    If this switch is not provided and the edge case is encountered, we will guess and attempt to comment out the selected lines, but we may guess wrong and comment out the lines above/below the selected lines.
243    If this switch is provided and the edge case is encountered, no lines will be commented out.
244
245    Edge Case:
246    - When the selected text occurs multiple times in the document, directly above or below the selected text.
247
248    Example:
249    abc
250    abc
251    abc
252
253    - If only the first two lines are selected, when you run this command it may comment out the 1st and 2nd lines correctly, or it may comment out the 2nd and 3rd lines, depending on
254    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
255    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get commented out, so it shouldn't be a big deal.
256    But if it bugs you, you can provide this switch.
257#>
258    CommentOrUncommentIseSelectedLines -CommentLines $true -DoNothingWhenNotCertainOfWhichLinesToSelect $DoNothingWhenNotCertainOfWhichLinesToComment
259}
260
261function Uncomment-IseSelectedLines([switch]$DoNothingWhenNotCertainOfWhichLinesToUncomment)
262{
263<#
264    .SYNOPSIS
265    Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
266    If no text is selected, it will uncomment the line that the caret is on.
267
268    .DESCRIPTION
269    Removes the comment character from the start of each line of the selected text in the current PS ISE file (if it is commented out).
270    If no text is selected, it will uncomment the line that the caret is on.
271
272    .PARAMETER DoNothingWhenNotCertainOfWhichLinesToUncomment
273    Under the following edge case we can't determine for sure which lines in the file are selected.
274    If this switch is not provided and the edge case is encountered, we will guess and attempt to uncomment the selected lines, but we may guess wrong and uncomment out the lines above/below the selected lines.
275    If this switch is provided and the edge case is encountered, no lines will be uncommentet.
276
277    Edge Case:
278    - When the selected text occurs multiple times in the document, directly above or below the selected text.
279
280    Example:
281    abc
282    abc
283    abc
284
285    - If only the first two lines are selected, when you run this command it may uncomment the 1st and 2nd lines correctly, or it may uncomment the 2nd and 3rd lines, depending on
286    if the caret is on the 1st line or 2nd line when selecting the text (i.e. the text is selected bottom-to-top vs. top-to-bottom).
287    - Since the lines are typically identical for this edge case to occur, you likely won't really care which 2 of the 3 lines get uncommented, so it shouldn't be a big deal.
288    But if it bugs you, you can provide this switch.
289#>
290    CommentOrUncommentIseSelectedLines -CommentLines $false -DoNothingWhenNotCertainOfWhichLinesToSelect $DoNothingWhenNotCertainOfWhichLinesToUncomment
291}
292
293#==========================================================
294# Add ISE Add-ons.
295#==========================================================
296
297# Add a new option in the Add-ons menu to comment all selected lines.
298if (!($psISE.CurrentPowerShellTab.AddOnsMenu.Submenus | Where-Object { $_.DisplayName -eq "Comment Selected Lines" }))
299{
300    $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Comment Selected Lines",{Comment-IseSelectedLines},"Ctrl+K")
301}
302
303# Add a new option in the Add-ons menu to uncomment all selected lines.
304if (!($psISE.CurrentPowerShellTab.AddOnsMenu.Submenus | Where-Object { $_.DisplayName -eq "Uncomment Selected Lines" }))
305{
306    $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Uncomment Selected Lines",{Uncomment-IseSelectedLines},"Ctrl+Shift+K")
307}