├── LICENSE ├── Media ├── Get-DiskPartDisk.png ├── Get-DiskPartVolume.png ├── Invoke-DiskPartScript-Offline.png └── Invoke-DiskPartScript-Online.png ├── PSDiskPart ├── Get-DiskPartDisk.ps1 ├── Get-DiskPartVDisk.ps1 ├── Get-DiskPartVolume.ps1 ├── Invoke-DiskPartScript.ps1 ├── PSDiskPart.Format.ps1xml ├── PSDiskPart.psd1 ├── PSDiskPart.psm1 └── Private │ └── Add-ObjectDetail.ps1 ├── README.md ├── Tests ├── PSDiskPart.Tests.ps1 └── appveyor.pester.ps1 └── appveyor.yml /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Warren F. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Media/Get-DiskPartDisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamblingCookieMonster/PSDiskPart/f344a46424c1b98dcdc1625c0cda2054a94cff40/Media/Get-DiskPartDisk.png -------------------------------------------------------------------------------- /Media/Get-DiskPartVolume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamblingCookieMonster/PSDiskPart/f344a46424c1b98dcdc1625c0cda2054a94cff40/Media/Get-DiskPartVolume.png -------------------------------------------------------------------------------- /Media/Invoke-DiskPartScript-Offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamblingCookieMonster/PSDiskPart/f344a46424c1b98dcdc1625c0cda2054a94cff40/Media/Invoke-DiskPartScript-Offline.png -------------------------------------------------------------------------------- /Media/Invoke-DiskPartScript-Online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamblingCookieMonster/PSDiskPart/f344a46424c1b98dcdc1625c0cda2054a94cff40/Media/Invoke-DiskPartScript-Online.png -------------------------------------------------------------------------------- /PSDiskPart/Get-DiskPartDisk.ps1: -------------------------------------------------------------------------------- 1 | Function Get-DiskPartDisk 2 | { 3 | <# 4 | .SYNOPSIS 5 | Run a LIST DISK and parse the output into objects, from one or more remote systems 6 | 7 | .FUNCTIONALITY 8 | Computers 9 | 10 | .DESCRIPTION 11 | Run a LIST DISK and parse the output into objects, from one or more remote systems. 12 | 13 | Get-Help Invoke-DiskPartScript -Full for details on implementation of remote calls 14 | 15 | .PARAMETER ComputerName 16 | Computer(s) to run command on. 17 | 18 | .EXAMPLE 19 | Get-DiskPartDisk -computername c-is-hyperv-1 20 | 21 | # Run 'list disk' on c-is-hyperv-1. 22 | 23 | .LINK 24 | https://github.com/RamblingCookieMonster/PSDiskPart 25 | 26 | .LINK 27 | Invoke-DiskPartScript 28 | 29 | .LINK 30 | Get-DiskPartDisk 31 | 32 | .LINK 33 | Get-DiskPartVolume 34 | 35 | .LINK 36 | Get-DiskPartVDisk 37 | 38 | .NOTES 39 | Thanks to Adam Conkle https://gallery.technet.microsoft.com/DiskPartexe-Powershell-0f7a1bab 40 | 41 | #> 42 | [OutputType('System.Management.Automation.PSObject')] 43 | [CmdletBinding()] 44 | param ( 45 | [Parameter( 46 | ValueFromPipeline=$true, 47 | ValueFromPipelineByPropertyName=$true)] 48 | [string[]]$ComputerName = $env:COMPUTERNAME 49 | ) 50 | 51 | Process 52 | { 53 | 54 | 55 | foreach($Computer in $ComputerName) 56 | { 57 | $dpscript = "list disk`n" 58 | 59 | Try 60 | { 61 | $Output = $Null 62 | if($Computer -eq $env:COMPUTERNAME) 63 | { 64 | $Output = $dpscript | diskpart 65 | } 66 | else 67 | { 68 | $Output = ( Invoke-DiskPartScript -ComputerName $Computer -DiskPartText $dpscript -Raw -ErrorAction stop ) -split "`n" 69 | } 70 | } 71 | Catch 72 | { 73 | Write-Error $_ 74 | Continue 75 | } 76 | 77 | $Disks = ForEach ($Line in $Output) 78 | { 79 | If ($Line.StartsWith(" Disk")) 80 | { 81 | $Line 82 | } 83 | } 84 | 85 | $DiskCount = $Disks.Count 86 | 87 | For ($i=1;$i -le ($DiskCount - 1);$i++) 88 | { 89 | $currLine = $Disks[$i] 90 | $currLine -Match " Disk (?...) +(?.............) +(?.......) +(?.......) +(?...) +(?...)" | Out-Null 91 | $DiskObj = @{ 92 | "ComputerName" = $Computer 93 | "DiskNumber" = $Matches['disknum'].Trim() 94 | "Status" = $Matches['sts'].Trim() 95 | "Size" = $Matches['sz'].Trim() 96 | "Free" = $Matches['fr'].Trim() 97 | "Dyn" = $Matches['dyn'].Trim() 98 | "Gpt" = $Matches['gpt'].Trim() 99 | } 100 | 101 | $dpscript = "select disk $($DiskObj.DiskNumber)`ndetail disk`n" 102 | 103 | Try 104 | { 105 | $Output = $Null 106 | if($Computer -eq $env:COMPUTERNAME) 107 | { 108 | $Output = $dpscript | diskpart 109 | } 110 | else 111 | { 112 | $Output = ( Invoke-DiskPartScript -ComputerName $Computer -DiskPartText $dpscript -Raw -ErrorAction stop ) -split "`n" 113 | } 114 | } 115 | Catch 116 | { 117 | Write-Error $_ 118 | Continue 119 | } 120 | 121 | ForEach ($Line in $Output) 122 | { 123 | If ($Line -cmatch "Disk ID" -and $Line -match ":") 124 | { 125 | $DiskObj.Add( "DiskID", $Line.Split(":")[1].Trim()) 126 | } 127 | ElseIf ($Line.StartsWith("Type") -and $Line -match ":") 128 | { 129 | $DiskObj.Add( "DetailType", $Line.Split(":")[1].Trim()) 130 | } 131 | ElseIf ($Line.StartsWith("Status") -and $Line -match ":") 132 | { 133 | $DiskObj.Add( "DetailStatus", $Line.Split(":")[1].Trim()) 134 | } 135 | ElseIf ($Line.StartsWith("Path") -and $Line -match ":") 136 | { 137 | $DiskObj.Add( "Path", $Line.Split(":")[1].Trim()) 138 | } 139 | ElseIf ($Line.StartsWith("Target") -and $Line -match ":") 140 | { 141 | $DiskObj.Add( "Target", $Line.Split(":")[1].Trim()) 142 | } 143 | ElseIf ($Line.StartsWith("LUN ID") -and $Line -match ":") 144 | { 145 | $DiskObj.Add( "LUNID", $Line.Split(":")[1].Trim()) 146 | } 147 | ElseIf ($Line.StartsWith("Location Path") -and $Line -match ":") 148 | { 149 | $DiskObj.Add( "LocationPath", $Line.Split(":")[1].Trim()) 150 | } 151 | ElseIf ($Line.StartsWith("Current Read-only State") -and $Line -match ":") 152 | { 153 | $DiskObj.Add( "CurrentReadOnlyState", $Line.Split(":")[1].Trim()) 154 | } 155 | ElseIf ($Line.StartsWith("Read-only") -and $Line -match ":") 156 | { 157 | $DiskObj.Add( "ReadOnly", $Line.Split(":")[1].Trim()) 158 | } 159 | ElseIf ($Line.StartsWith("Boot Disk") -and $Line -match ":") 160 | { 161 | $DiskObj.Add( "BootDisk", $Line.Split(":")[1].Trim()) 162 | } 163 | ElseIf ($Line.StartsWith("Pagefile Disk") -and $Line -match ":") 164 | { 165 | $DiskObj.Add( "PagefileDisk", $Line.Split(":")[1].Trim()) 166 | } 167 | ElseIf ($Line.StartsWith("Hibernation File Disk") -and $Line -match ":") 168 | { 169 | $DiskObj.Add( "HibernationFileDisk", $Line.Split(":")[1].Trim()) 170 | } 171 | ElseIf ($Line.StartsWith("Crashdump Disk") -and $Line -match ":") 172 | { 173 | $DiskObj.Add( "CrashdumpDisk", $Line.Split(":")[1].Trim()) 174 | } 175 | ElseIf ($Line.StartsWith("Clustered Disk") -and $Line -match ":") 176 | { 177 | $DiskObj.Add( "ClusteredDisk", $Line.Split(":")[1].Trim()) 178 | } 179 | } 180 | 181 | New-Object -TypeName PSObject -Property $DiskObj | 182 | Select-Object -Property ComputerName, 183 | DiskNumber, 184 | Status, 185 | Size, 186 | Free, 187 | Dyn, 188 | Gpt, 189 | DiskID, 190 | DetailType, 191 | DetailStatus, 192 | Path, 193 | Target, 194 | LUNID, 195 | LocationPath, 196 | CurrentReadOnlyState, 197 | ReadOnly, 198 | BootDisk, 199 | PagefileDisk, 200 | HibernationFileDisk, 201 | CrashdumpDisk, 202 | ClusteredDisk | 203 | Add-ObjectDetail -TypeName 'DiskPart.Disk' 204 | 205 | } 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /PSDiskPart/Get-DiskPartVDisk.ps1: -------------------------------------------------------------------------------- 1 | Function Get-DiskPartVDisk 2 | { 3 | <# 4 | .SYNOPSIS 5 | Run a LIST VDISK and parse the output into objects, from one or more remote systems 6 | 7 | .FUNCTIONALITY 8 | Computers 9 | 10 | .DESCRIPTION 11 | Run a LIST VDISK and parse the output into objects, from one or more remote systems. 12 | 13 | Get-Help Invoke-DiskPartScript -Full for details on implementation of remote calls 14 | 15 | .PARAMETER ComputerName 16 | Computer(s) to run command on. 17 | 18 | .EXAMPLE 19 | Get-DiskPartVDisk -computername c-is-hyperv-1 20 | 21 | # Run 'list vdisk' on c-is-hyperv-1. 22 | 23 | .LINK 24 | https://github.com/RamblingCookieMonster/PSDiskPart 25 | 26 | .LINK 27 | Invoke-DiskPartScript 28 | 29 | .LINK 30 | Get-DiskPartDisk 31 | 32 | .LINK 33 | Get-DiskPartVolume 34 | 35 | .LINK 36 | Get-DiskPartVDisk 37 | 38 | .NOTES 39 | Thanks to Adam Conkle https://gallery.technet.microsoft.com/DiskPartexe-Powershell-0f7a1bab 40 | 41 | #> 42 | [OutputType('System.Management.Automation.PSObject')] 43 | [CmdletBinding()] 44 | param ( 45 | [Parameter( 46 | ValueFromPipeline=$true, 47 | ValueFromPipelineByPropertyName=$true)] 48 | [string[]]$ComputerName = $env:COMPUTERNAME 49 | ) 50 | 51 | Process 52 | { 53 | 54 | 55 | foreach($Computer in $ComputerName) 56 | { 57 | $dpscript = "list vdisk`n" 58 | Try 59 | { 60 | $Output = $Null 61 | if($Computer -eq $env:COMPUTERNAME) 62 | { 63 | $Output = $dpscript | diskpart 64 | } 65 | else 66 | { 67 | $Output = ( Invoke-DiskPartScript -ComputerName $Computer -DiskPartText $dpscript -Raw -ErrorAction stop ) -split "`n" 68 | } 69 | } 70 | Catch 71 | { 72 | Write-Error $_ 73 | Continue 74 | } 75 | 76 | $VDisks = ForEach ($Line in $Output) 77 | { 78 | If ($Line.StartsWith(" VDisk")) 79 | { 80 | $Line 81 | } 82 | } 83 | 84 | $VDiskCount = $VDisks.Count 85 | 86 | For ($i=1; $i -le ($VDiskCount - 1); $i++) 87 | { 88 | $currLine = $VDisks[$i] 89 | $currLine -Match " VDisk (?...) +(?........) +(?....................) +(?.........) +(?.+)" | Out-Null 90 | $VDiskObj = @{ 91 | "ComputerName" = $Computer 92 | "VDiskNumber" = $Matches['vdisknum'].Trim() 93 | "PhysicalDiskNumber" = $Matches['phydisknum'].Trim() 94 | "State" = $Matches['state'].Trim() 95 | "Type" = $Matches['type'].Trim() 96 | "File" = $Matches['file'].Trim() 97 | } 98 | 99 | $dpscript = "select vdisk file=$($VDiskObj.File)`ndetail vdisk`n" 100 | 101 | Try 102 | { 103 | $Output = $Null 104 | if($Computer -eq $env:COMPUTERNAME) 105 | { 106 | $Output = $dpscript | diskpart 107 | } 108 | else 109 | { 110 | $Output = ( Invoke-DiskPartScript -ComputerName $Computer -DiskPartText $dpscript -Raw -ErrorAction stop ) -split "`n" 111 | } 112 | } 113 | Catch 114 | { 115 | Write-Error $_ 116 | Continue 117 | } 118 | 119 | ForEach ($Line in $Output) 120 | { 121 | If ($Line -cmatch "Device type ID" -and $Line -match ":") 122 | { 123 | $VDiskObj.Add( "DeviceTypeId", $Line.Split(":")[1].Trim()) 124 | } 125 | ElseIf ($Line.StartsWith("Vendor ID") -and $Line -match ":") 126 | { 127 | $VDiskObj.Add( "VendorId", $Line.Split(":")[1].Trim()) 128 | } 129 | ElseIf ($Line.StartsWith("State") -and $Line -match ":") 130 | { 131 | $VDiskObj.Add("DetailState", $Line.Split(":")[1].Trim()) 132 | } 133 | ElseIf ($Line.StartsWith("Virtual size") -and $Line -match ":") 134 | { 135 | $VDiskObj.Add("VirtualSize", $Line.Split(":")[1].Trim()) 136 | } 137 | ElseIf ($Line.StartsWith("Physical size") -and $Line -match ":") 138 | { 139 | $VDiskObj.Add("PhysicalSize", $Line.Split(":")[1].Trim()) 140 | } 141 | ElseIf ($Line.StartsWith("Is Child") -and $Line -match ":") 142 | { 143 | $VDiskObj.Add( "IsChild", $Line.Split(":")[1].Trim()) 144 | } 145 | ElseIf ($Line.StartsWith("Parent Filename") -and $Line -match ":") 146 | { 147 | $VDiskObj.Add("ParentFileName", $Line.Split(":")[1].Trim()) 148 | } 149 | ElseIf ($Line.StartsWith("Associated disk#") -and $Line -match ":") 150 | { 151 | $VDiskObj.Add("AssociatedDiskNum", $Line.Split(":")[1].Trim()) 152 | } 153 | } 154 | 155 | New-Object -TypeName PSObject -Property $VDiskObj | 156 | Select-Object -Property ComputerName, 157 | VDiskNumber, 158 | PhysicalDiskNumber, 159 | State, 160 | Type, 161 | File, 162 | DeviceTypeId, 163 | VendorId, 164 | DetailState, 165 | VirtualSize, 166 | PhysicalSize, 167 | IsChild, 168 | ParentFileName, 169 | AssociatedDiskNum 170 | } 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /PSDiskPart/Get-DiskPartVolume.ps1: -------------------------------------------------------------------------------- 1 | Function Get-DiskPartVolume 2 | { 3 | <# 4 | .SYNOPSIS 5 | Run a LIST VOLUME and parse the output into objects, from one or more remote systems 6 | 7 | .FUNCTIONALITY 8 | Computers 9 | 10 | .DESCRIPTION 11 | Run a LIST VOLUME and parse the output into objects, from one or more remote systems. 12 | 13 | Get-Help Invoke-DiskPartScript -Full for details on implementation of remote calls 14 | 15 | .PARAMETER ComputerName 16 | Computer(s) to run command on. 17 | 18 | .EXAMPLE 19 | Get-DiskPartVolume -computername c-is-hyperv-1 20 | 21 | # Run 'list volume' on c-is-hyperv-1. 22 | 23 | .LINK 24 | https://github.com/RamblingCookieMonster/PSDiskPart 25 | 26 | .LINK 27 | Invoke-DiskPartScript 28 | 29 | .LINK 30 | Get-DiskPartDisk 31 | 32 | .LINK 33 | Get-DiskPartVolume 34 | 35 | .LINK 36 | Get-DiskPartVDisk 37 | 38 | .NOTES 39 | Thanks to Adam Conkle https://gallery.technet.microsoft.com/DiskPartexe-Powershell-0f7a1bab 40 | 41 | #> 42 | [OutputType('System.Management.Automation.PSObject')] 43 | [CmdletBinding()] 44 | param ( 45 | [Parameter( 46 | ValueFromPipeline=$true, 47 | ValueFromPipelineByPropertyName=$true)] 48 | [string[]]$ComputerName = $env:COMPUTERNAME 49 | ) 50 | 51 | Process 52 | { 53 | 54 | foreach($Computer in $ComputerName) 55 | { 56 | 57 | $dpscript = "list volume`n" 58 | Try 59 | { 60 | $Output = $Null 61 | if($Computer -eq $env:COMPUTERNAME) 62 | { 63 | $Output = $dpscript | diskpart 64 | } 65 | else 66 | { 67 | $Output = ( Invoke-DiskPartScript -ComputerName $Computer -DiskPartText $dpscript -Raw -ErrorAction stop ) -split "`n" 68 | } 69 | } 70 | Catch 71 | { 72 | Write-Error $_ 73 | Continue 74 | } 75 | 76 | 77 | $Vols = ForEach ($Line in $Output) 78 | { 79 | If ($Line.StartsWith(" Volume")) 80 | { 81 | $Line 82 | } 83 | } 84 | 85 | $VolCount = $Vols.Count 86 | 87 | For ($i=1;$i -le ($Vols.count-1);$i++) 88 | { 89 | $currLine = $Vols[$i] 90 | $currLine -Match " Volume (?...) +(?...) +(?...........) +(?.....) +(?..........) +(?.......) +(?.........) +(?........)" | Out-Null 91 | $VolObj = @{ 92 | "ComputerName" = $Computer 93 | "VolumeNumber" = $Matches['volnum'].Trim() 94 | "Letter" = $Matches['drltr'].Trim() 95 | "Label" = $Matches['lbl'].Trim() 96 | "FileSystem" = $Matches['fs'].Trim() 97 | "Type" = $Matches['typ'].Trim() 98 | "Size" = $Matches['sz'].Trim() 99 | "Status" = $Matches['sts'].Trim() 100 | "Info" = $Matches['nfo'].Trim() 101 | } 102 | 103 | $dpscript = "select volume $($VolObj.VolumeNumber)`ndetail volume`n" 104 | 105 | Try 106 | { 107 | $Output = $Null 108 | if($Computer -eq $env:COMPUTERNAME) 109 | { 110 | $Output = $dpscript | diskpart 111 | } 112 | else 113 | { 114 | $Output = ( Invoke-DiskPartScript -ComputerName $Computer -DiskPartText $dpscript -Raw -ErrorAction stop ) -split "`n" 115 | } 116 | } 117 | Catch 118 | { 119 | Write-Error $_ 120 | Continue 121 | } 122 | 123 | 124 | ForEach ($Line in $Output) 125 | { 126 | If ($Line.StartsWith("Read-only") -and $Line -match ":") 127 | { 128 | $VolObj.Add( "ReadOnly", $Line.Split(":")[1].Trim() ) 129 | } 130 | ElseIf ($Line.StartsWith("Hidden") -and $Line -match ":") 131 | { 132 | $VolObj.Add( "Hidden", $Line.Split(":")[1].Trim()) 133 | } 134 | ElseIf ($Line.StartsWith("No Default Drive Letter") -and $Line -match ":") 135 | { 136 | $VolObj.Add( "NoDefaultDriveLetter", $Line.Split(":")[1].Trim()) 137 | } 138 | ElseIf ($Line.StartsWith("Shadow Copy") -and $Line -match ":") 139 | { 140 | $VolObj.Add( "ShadowCopy", $Line.Split(":")[1].Trim() ) 141 | } 142 | ElseIf ($Line.StartsWith("Offline") -and $Line -match ":") 143 | { 144 | $VolObj.Add( "Offline", $Line.Split(":")[1].Trim()) 145 | } 146 | ElseIf ($Line.StartsWith("BitLocker Encrypted") -and $Line -match ":") 147 | { 148 | $VolObj.Add( "BitLockerEncrypted", $Line.Split(":")[1].Trim()) 149 | } 150 | ElseIf ($Line.StartsWith("Installable") -and $Line -match ":") 151 | { 152 | $VolObj.Add( "Installable", $Line.Split(":")[1].Trim() ) 153 | } 154 | ElseIf ($Line.StartsWith("Volume Capacity") -and $Line -match ":") 155 | { 156 | $VolObj.Add( "VolumeCapacity", $Line.Split(":")[1].Trim()) 157 | } 158 | ElseIf ($Line.StartsWith("Volume Free Space") -and $Line -match ":") 159 | { 160 | $VolObj.Add( "VolumeFreeSpace", $Line.Split(":")[1].Trim()) 161 | } 162 | } 163 | 164 | New-Object -TypeName PSObject -Property $VolObj | 165 | Select-Object -Property ComputerName, 166 | VolumeNumber, 167 | Letter, 168 | Label, 169 | FileSystem, 170 | Type, 171 | Size, 172 | Status, 173 | Info, 174 | ReadOnly, 175 | Hidden, 176 | NoDefaultDriveLetter, 177 | ShadowCopy, 178 | Offline, 179 | BitLockerEncrypted, 180 | Installable, 181 | VolumeCapacity, 182 | VolumeFreeSpace | 183 | Add-ObjectDetail -TypeName 'DiskPart.Volume' 184 | } 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /PSDiskPart/Invoke-DiskPartScript.ps1: -------------------------------------------------------------------------------- 1 | Function Invoke-DiskPartScript { 2 | <# 3 | .SYNOPSIS 4 | Run a DiskPart script on one or more remote systems 5 | 6 | .FUNCTIONALITY 7 | Computers 8 | 9 | .DESCRIPTION 10 | Run a DiskPart script on one or more remote systems. Results and errors sent to text files on that system, returned by this function, and filtered as specified. 11 | 12 | Command runs via cmd /c on the remote system, not PowerShell. 13 | Command invoked via Win32_Process Create method. 14 | 15 | Returns an object containing the computer, results, errors, command, and pattern. 16 | 17 | Be very, very catreful with this. DiskPart can do evil things. Use this at your own risk. 18 | 19 | .PARAMETER ComputerName 20 | Computer(s) to run command on. 21 | 22 | .PARAMETER DiskPartText 23 | Text to run as a DiskPart script 24 | 25 | .PARAMETER Pattern 26 | Optional regular expression to filter results 27 | 28 | .PARAMETER ScriptFile 29 | Temporary file to store DiskPart script on remote system. Must be relative to remote system (not a file share). Default is "C:\DiskPartScript.txt" 30 | 31 | .PARAMETER TempOutputFile 32 | Temporary file to store results on remote system. Must be relative to remote system (not a file share). Default is "C:\DiskPartOutput.txt" 33 | 34 | .PARAMETER TempErrorFile 35 | Temporary file to store redirected errors. Must be relative to remote system (not a file share). Defaults to "C:\DiskPartError.txt" 36 | 37 | .PARAMETER Raw 38 | Return only the output from the command 39 | 40 | .EXAMPLE 41 | Invoke-DiskPartScript -computername wbf, c-is-hyperv-1 -DiskPartText "list volume" 42 | 43 | # Run 'list volume' on wbf and c-is-hyperv-1. 44 | 45 | .EXAMPLE 46 | Invoke-DiskPartScript -computername wbf, c-is-hyperv-1 -DiskPartText "list disk" -pattern "offline" | select-object computer, results 47 | 48 | # Run 'list disk' on wbf and c-is-hyperv-1. Filter output to lines that match 'offline'. Only display the Computer and Results 49 | 50 | .EXAMPLE 51 | $ScriptContent = Get-Content C:\DiskPart.txt -Raw 52 | Invoke-DiskPartScript -ComputerName c-is-hyperv-1 -DiskPartText $ScriptContent 53 | 54 | # Get the content of an existing diskpart script, use it against C-IS-HYPERV-1. 55 | 56 | .LINK 57 | https://github.com/RamblingCookieMonster/PSDiskPart 58 | 59 | .LINK 60 | Invoke-DiskPartScript 61 | 62 | .LINK 63 | Get-DiskPartDisk 64 | 65 | .LINK 66 | Get-DiskPartVolume 67 | 68 | .LINK 69 | Get-DiskPartVDisk 70 | 71 | #> 72 | [OutputType('System.Management.Automation.PSObject', 'System.String')] 73 | [CmdletBinding()] 74 | param( 75 | [Parameter( 76 | ValueFromPipeline=$true, 77 | ValueFromPipelineByPropertyName=$true)] 78 | [string[]]$ComputerName = $env:COMPUTERNAME, 79 | 80 | $DiskPartText = "list disk", 81 | 82 | $Pattern, 83 | 84 | $ScriptFile = "C:\DiskPartScript.txt", 85 | 86 | $TempOutputFile = "C:\DiskPartOutput.txt", 87 | 88 | $TempErrorFile = "C:\DiskPartError.txt", 89 | 90 | [switch]$Raw 91 | ) 92 | 93 | Begin 94 | { 95 | 96 | Function Wait-Path { 97 | [cmdletbinding()] 98 | param ( 99 | [string[]]$Path, 100 | [int]$Timeout = 5, 101 | [int]$Interval = 1, 102 | [switch]$Passthru 103 | ) 104 | 105 | $StartDate = Get-Date 106 | $First = $True 107 | 108 | Do 109 | { 110 | #Only sleep if this isn't the first run 111 | if($First -eq $True) 112 | { 113 | $First = $False 114 | } 115 | else 116 | { 117 | Start-Sleep -Seconds $Interval 118 | } 119 | 120 | #Test paths and collect output 121 | [bool[]]$Tests = foreach($PathItem in $Path) 122 | { 123 | Try 124 | { 125 | if(Test-Path $PathItem -ErrorAction stop) 126 | { 127 | Write-Verbose "'$PathItem' exists" 128 | $True 129 | } 130 | else 131 | { 132 | Write-Verbose "Waiting for '$PathItem'" 133 | $False 134 | } 135 | } 136 | Catch 137 | { 138 | Write-Error "Error testing path '$PathItem': $_" 139 | $False 140 | } 141 | } 142 | 143 | # Identify whether we can see everything 144 | $Return = $Tests -notcontains $False -and $Tests -contains $True 145 | 146 | # Poor logic, but we break the Until here 147 | # Did we time out? 148 | # Error if we are not passing through 149 | if ( ((Get-Date) - $StartDate).TotalSeconds -gt $Timeout) 150 | { 151 | if( $Passthru ) 152 | { 153 | $False 154 | break 155 | } 156 | else 157 | { 158 | Throw "Timed out waiting for paths $($Path -join ", ")" 159 | } 160 | } 161 | elseif($Return) 162 | { 163 | if( $Passthru ) 164 | { 165 | $True 166 | } 167 | break 168 | } 169 | } 170 | Until( $False ) # We break out above 171 | } 172 | 173 | #Initialize command string and variables for status tracking 174 | [string]$cmd = "cmd /c diskpart /s $ScriptFile > $TempOutputFile 2> $tempErrorFile" 175 | } 176 | 177 | Process 178 | { 179 | foreach($Computer in $ComputerName){ 180 | 181 | Write-Verbose "Running '$cmd' on '$computer'" 182 | 183 | #define remote file path - computername, drive, folder path 184 | $remoteTempOutputFile = "\\{0}\{1}`${2}" -f "$computer", (split-path $TempOutputFile -qualifier).TrimEnd(":"), (Split-Path $TempOutputFile -noqualifier) 185 | $remoteTempErrorFile = "\\{0}\{1}`${2}" -f "$computer", (split-path $tempErrorFile -qualifier).TrimEnd(":"), (Split-Path $tempErrorFile -noqualifier) 186 | $remoteScriptFile = "\\{0}\{1}`${2}" -f "$computer", (split-path $ScriptFile -qualifier).TrimEnd(":"), (Split-Path $ScriptFile -noqualifier) 187 | 188 | #Attempt to delete any previous results, run command 189 | Try 190 | { 191 | Remove-Item -Path $remoteTempOutputFile, $remoteTempErrorFile -Force -ErrorAction SilentlyContinue -Confirm:$False 192 | Set-Content -Path $remoteScriptFile -Value $DiskPartText -force -ErrorAction stop 193 | } 194 | Catch 195 | { 196 | Write-Error "Error preparing $computer`n:$_" 197 | Continue 198 | } 199 | Try 200 | { 201 | Wait-Path -Path $remoteScriptFile -Timeout 10 -Interval .5 -ErrorAction stop 202 | $processID = (Invoke-WmiMethod -class Win32_process -name Create -ArgumentList $cmd -ComputerName $computer -ErrorAction Stop).processid 203 | } 204 | Catch 205 | { 206 | Write-Error "Error running '$cmd' on $computer" 207 | Continue 208 | } 209 | 210 | #wait for process to complete 211 | while ( 212 | $( 213 | try 214 | { 215 | Get-Process -Id $processid -ComputerName $computer -ErrorAction Stop 216 | } 217 | catch 218 | { 219 | if($_ -like "Cannot find a process with*") 220 | { 221 | $FALSE 222 | } 223 | else 224 | { 225 | Write-Error "Error checking for PID $ProcessId on $Computer`: $_" 226 | } 227 | } 228 | ) 229 | ) 230 | { Start-Sleep -seconds 2 } 231 | 232 | #gather results 233 | if( Wait-Path $remoteTempOutputFile -Timeout 15 -Interval .5 -Passthru ) 234 | { 235 | if($pattern) 236 | { 237 | $Results = ( Select-String -Path $remoteTempOutputFile -Pattern $Pattern | Select -ExpandProperty Line ) -join "`n" 238 | } 239 | else 240 | { 241 | if($PSVersionTable.PSVersion.Major -ge 3) 242 | { 243 | $results = Get-Content -Path $remoteTempOutputFile -Raw 244 | } 245 | else 246 | { 247 | $results = ( Get-Content -Path $remoteTempOutputFile -ReadCount 1500 ) -join "`n" 248 | } 249 | } 250 | } 251 | else 252 | { 253 | $results = "Results from '$TempOutputFile' on $computer converted to '$remoteTempOutputFile'. This path is not accessible from your system." 254 | } 255 | 256 | #gather errors 257 | if( Wait-Path -Path $remoteTempErrorFile -Timeout 10 -Interval .5 -Passthru ) 258 | { 259 | $errors = Get-Content -Path $remoteTempErrorFile 260 | } 261 | else 262 | { 263 | $results = "Errors from '$tempErrorFile' on $computer converted to '$remoteTempErrorFile'. This path is not accessible from your system." 264 | } 265 | 266 | if($Raw) 267 | { 268 | $Results 269 | if($Errors) 270 | { 271 | Write-Error $Errors 272 | } 273 | } 274 | else 275 | { 276 | #write out the results 277 | [pscustomobject] @{ 278 | Computer = $computer 279 | Results = $Results 280 | Errors = $errors 281 | Command = $cmd 282 | Pattern = $Pattern 283 | } 284 | } 285 | 286 | Remove-Item -Path $remoteTempOutputFile, $remoteScriptFile, $remoteTempErrorFile -Force -ErrorAction SilentlyContinue 287 | } 288 | } 289 | 290 | } -------------------------------------------------------------------------------- /PSDiskPart/PSDiskPart.Format.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Default 6 | 7 | DiskPart.Disk 8 | 9 | 10 | 11 | 12 | 15 13 | 14 | 15 | 10 16 | 17 | 18 | 7 19 | 20 | 21 | 10 22 | 23 | 24 | 8 25 | 26 | 27 | 4 28 | 29 | 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ComputerName 39 | 40 | 41 | DiskNumber 42 | 43 | 44 | Status 45 | 46 | 47 | DetailType 48 | 49 | 50 | BootDisk 51 | 52 | 53 | Size 54 | 55 | 56 | Free 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Default 65 | 66 | DiskPart.Volume 67 | 68 | 69 | 70 | 71 | 15 72 | 73 | 74 | 6 75 | 76 | 77 | 15 78 | 79 | 80 | 10 81 | 82 | 83 | 9 84 | 85 | 86 | 8 87 | 88 | 89 | 14 90 | 91 | 92 | 15 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ComputerName 101 | 102 | 103 | Letter 104 | 105 | 106 | Label 107 | 108 | 109 | FileSystem 110 | 111 | 112 | Type 113 | 114 | 115 | Status 116 | 117 | 118 | VolumeCapacity 119 | 120 | 121 | VolumeFreespace 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /PSDiskPart/PSDiskPart.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | # Script module or binary module file associated with this manifest. 4 | ModuleToProcess = 'PSDiskPart.psm1' 5 | 6 | # Version number of this module. 7 | ModuleVersion = '1.0' 8 | 9 | # ID used to uniquely identify this module 10 | GUID = '5df7639c-300b-43e2-8cd9-92904a708c67' 11 | 12 | # Author of this module 13 | Author = 'ramblingcookiemonster' 14 | 15 | # Company or vendor of this module 16 | CompanyName = '' 17 | 18 | # Copyright statement for this module 19 | # Copyright = '(c) 2014 ramblingcookiemonster. All rights reserved.' 20 | 21 | # Description of the functionality provided by this module 22 | Description = 'Work with DiskPart' 23 | 24 | # Minimum version of the Windows PowerShell engine required by this module 25 | PowerShellVersion = '2.0' 26 | 27 | # Name of the Windows PowerShell host required by this module 28 | # PowerShellHostName = '' 29 | 30 | # Minimum version of the Windows PowerShell host required by this module 31 | # PowerShellHostVersion = '' 32 | 33 | # Minimum version of Microsoft .NET Framework required by this module 34 | # DotNetFrameworkVersion = '' 35 | 36 | # Minimum version of the common language runtime (CLR) required by this module 37 | # CLRVersion = '' 38 | 39 | # Processor architecture (None, X86, Amd64) required by this module 40 | # ProcessorArchitecture = '' 41 | 42 | # Modules that must be imported into the global environment prior to importing this module 43 | # RequiredModules = @() 44 | 45 | # Assemblies that must be loaded prior to importing this module 46 | # RequiredAssemblies = @() 47 | 48 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 49 | # ScriptsToProcess = @() 50 | 51 | # Type files (.ps1xml) to be loaded when importing this module 52 | # TypesToProcess = @() 53 | 54 | # Format files (.ps1xml) to be loaded when importing this module 55 | FormatsToProcess = @('PSDiskPart.Format.ps1xml') 56 | 57 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 58 | # NestedModules = @() 59 | 60 | # Functions to export from this module 61 | FunctionsToExport = @( 62 | 'Get-DiskPartDisk', 63 | 'Get-DiskPartVDisk', 64 | 'Get-DiskPartVolume', 65 | 'Invoke-DiskPartScript' 66 | ) 67 | 68 | # Cmdlets to export from this module 69 | # CmdletsToExport = '*' 70 | 71 | # Variables to export from this module 72 | # VariablesToExport = '*' 73 | 74 | # Aliases to export from this module 75 | # AliasesToExport = '*' 76 | 77 | # List of all modules packaged with this module 78 | # ModuleList = @() 79 | 80 | # List of all files packaged with this module 81 | # FileList = @() 82 | 83 | # Private data to pass to the module specified in RootModule/ModuleToProcess 84 | # PrivateData = '' 85 | 86 | # HelpInfo URI of this module 87 | # HelpInfoURI = '' 88 | 89 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 90 | # DefaultCommandPrefix = '' 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /PSDiskPart/PSDiskPart.psm1: -------------------------------------------------------------------------------- 1 | #handle PS2 2 | if(-not $PSScriptRoot) 3 | { 4 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 5 | } 6 | 7 | #Get public and private function definition files. 8 | $Public = Get-ChildItem $PSScriptRoot\*.ps1 -ErrorAction SilentlyContinue 9 | $Private = Get-ChildItem $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue 10 | 11 | #Dot source the files 12 | Foreach($import in @($Public + $Private)) 13 | { 14 | Try 15 | { 16 | #PS2 compatibility 17 | if($import.fullname) 18 | { 19 | . $import.fullname 20 | } 21 | } 22 | Catch 23 | { 24 | Write-Error "Failed to import function '$($import.fullname)'" 25 | } 26 | } 27 | 28 | # We export functions in the psd1 file -------------------------------------------------------------------------------- /PSDiskPart/Private/Add-ObjectDetail.ps1: -------------------------------------------------------------------------------- 1 | function Add-ObjectDetail 2 | { 3 | <# 4 | .SYNOPSIS 5 | Decorate an object with 6 | - A TypeName 7 | - New properties 8 | - Default parameters 9 | 10 | .DESCRIPTION 11 | Helper function to decorate an object with 12 | - A TypeName 13 | - New properties 14 | - Default parameters 15 | 16 | .PARAMETER InputObject 17 | Object to decorate. Accepts pipeline input. 18 | 19 | .PARAMETER TypeName 20 | Typename to insert. 21 | 22 | This will show up when you use Get-Member against the resulting object. 23 | 24 | .PARAMETER PropertyToAdd 25 | Add these noteproperties. 26 | 27 | Format is a hashtable with Key (Property Name) = Value (Property Value). 28 | 29 | Example to add a One and Date property: 30 | 31 | -PropertyToAdd @{ 32 | One = 1 33 | Date = (Get-Date) 34 | } 35 | 36 | .PARAMETER DefaultProperties 37 | Change the default properties that show up 38 | 39 | .PARAMETER Passthru 40 | Whether to pass the resulting object on. Defaults to true 41 | 42 | .EXAMPLE 43 | # 44 | # Create an object to work with 45 | $Object = [PSCustomObject]@{ 46 | First = 'Cookie' 47 | Last = 'Monster' 48 | Account = 'CMonster' 49 | } 50 | 51 | #Add a type name and a random property 52 | Add-ObjectDetail -InputObject $Object -TypeName 'ApplicationX.Account' -PropertyToAdd @{ AnotherProperty = 5 } 53 | 54 | # First Last Account AnotherProperty 55 | # ----- ---- ------- --------------- 56 | # Cookie Monster CMonster 5 57 | 58 | #Verify that get-member shows us the right type 59 | $Object | Get-Member 60 | 61 | # TypeName: ApplicationX.Account ... 62 | 63 | .EXAMPLE 64 | # 65 | # Create an object to work with 66 | $Object = [PSCustomObject]@{ 67 | First = 'Cookie' 68 | Last = 'Monster' 69 | Account = 'CMonster' 70 | } 71 | 72 | #Add a random property, set a default property set so we only see two props by default 73 | Add-ObjectDetail -InputObject $Object -PropertyToAdd @{ AnotherProperty = 5 } -DefaultProperties Account, AnotherProperty 74 | 75 | # Account AnotherProperty 76 | # ------- --------------- 77 | # CMonster 5 78 | 79 | #Verify that the other properties are around 80 | $Object | Select -Property * 81 | 82 | # First Last Account AnotherProperty 83 | # ----- ---- ------- --------------- 84 | # Cookie Monster CMonster 5 85 | 86 | .NOTES 87 | This breaks the 'do one thing' rule from certain perspectives... 88 | The goal is to decorate an object all in one shot 89 | 90 | This abstraction simplifies decorating an object, with a slight trade-off in performance. For example: 91 | 92 | 10,000 objects, add a property and typename: 93 | Add-ObjectDetail: ~4.6 seconds 94 | Add-Member + PSObject.TypeNames.Insert: ~3 seconds 95 | 96 | Initial code borrowed from Shay Levy: 97 | http://blogs.microsoft.co.il/scriptfanatic/2012/04/13/custom-objects-default-display-in-powershell-30/ 98 | 99 | .LINK 100 | http://ramblingcookiemonster.github.io/Decorating-Objects/ 101 | 102 | .FUNCTIONALITY 103 | PowerShell Language 104 | #> 105 | [CmdletBinding()] 106 | param( 107 | [Parameter( Mandatory = $true, 108 | Position=0, 109 | ValueFromPipeline=$true )] 110 | [ValidateNotNullOrEmpty()] 111 | [psobject[]]$InputObject, 112 | 113 | [Parameter( Mandatory = $false, 114 | Position=1)] 115 | [string]$TypeName, 116 | 117 | [Parameter( Mandatory = $false, 118 | Position=2)] 119 | [System.Collections.Hashtable]$PropertyToAdd, 120 | 121 | [Parameter( Mandatory = $false, 122 | Position=3)] 123 | [ValidateNotNullOrEmpty()] 124 | [Alias('dp')] 125 | [System.String[]]$DefaultProperties, 126 | 127 | [boolean]$Passthru = $True 128 | ) 129 | 130 | Begin 131 | { 132 | if($PSBoundParameters.ContainsKey('DefaultProperties')) 133 | { 134 | # define a subset of properties 135 | $ddps = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet,$DefaultProperties 136 | $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]$ddps 137 | } 138 | } 139 | Process 140 | { 141 | foreach($Object in $InputObject) 142 | { 143 | switch ($PSBoundParameters.Keys) 144 | { 145 | 'PropertyToAdd' 146 | { 147 | foreach($Key in $PropertyToAdd.Keys) 148 | { 149 | #Add some noteproperties. Slightly faster than Add-Member. 150 | $Object.PSObject.Properties.Add( ( New-Object System.Management.Automation.PSNoteProperty($Key, $PropertyToAdd[$Key]) ) ) 151 | } 152 | } 153 | 'TypeName' 154 | { 155 | #Add specified type 156 | [void]$Object.PSObject.TypeNames.Insert(0,$TypeName) 157 | } 158 | 'DefaultProperties' 159 | { 160 | # Attach default display property set 161 | Add-Member -InputObject $Object -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers 162 | } 163 | } 164 | if($Passthru) 165 | { 166 | $Object 167 | } 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/u6gtkc7on8nm4kpi/branch/master?svg=true)](https://ci.appveyor.com/project/RamblingCookieMonster/PSDiskpart) 2 | 3 | DiskPart PowerShell Module 4 | ============= 5 | 6 | This is a PowerShell module for working with DiskPart. Newer Operating Systems include many commands to replace DiskPart; unfortunately, these have not been extended to down-level operating systems. 7 | 8 | Please be wary and read through this before using it. While it works in my environment, you know the risks of working with diskpart. 9 | 10 | Contributions to improve this would be more than welcome! 11 | 12 | Caveats: 13 | * Minimal testing. Not something you want to hear with DiskPart. 14 | 15 | #Functionality 16 | 17 | Get DISK information: 18 | * ![Get DISK information](/Media/Get-DiskPartDisk.png) 19 | 20 | Get VOLUME information: 21 | * ![Get VOLUME information](/Media/Get-DiskPartVolume.png) 22 | 23 | Offline a disk 24 | * ![Offline a disk](/Media/Invoke-DiskPartScript-Offline.png) 25 | 26 | Online a disk 27 | * ![Online a disk](/Media/Invoke-DiskPartScript-Online.png) 28 | 29 | #Instructions 30 | 31 | ```powershell 32 | # One time setup 33 | # Download the repository 34 | # Unblock the zip 35 | # Extract the PSDiskPart folder to a module path (e.g. $env:USERPROFILE\Documents\WindowsPowerShell\Modules\) 36 | 37 | # Import the module. 38 | Import-Module PSDiskPart #Alternatively, Import-Module \\Path\To\PSDiskPart 39 | 40 | # Get commands in the module 41 | Get-Command -Module PSDiskPart 42 | 43 | # Get help for a command 44 | Get-Help Get-DiskPartDisk -Full 45 | 46 | # Get details about disks on the local computer and c-is-hyperv-1 47 | Get-DiskPartDisk -computername $ENV:COMPUTERNAME, c-is-hyperv-1 48 | 49 | # Run a DiskPart script on SERVER1145, set disk 2 to online, clear the readonly attribute if it exists 50 | # Mind the here string. Ugly formatting necessary! 51 | 52 | Invoke-DiskPartScript -ComputerName SERVER1145 -DiskPartText @" 53 | select disk 2 54 | online disk 55 | attributes disk clear readonly 56 | "@ 57 | ``` 58 | 59 | #Notes 60 | 61 | * Thanks to Adam Conkle for the [disk part parsing pieces](https://gallery.technet.microsoft.com/DiskPartexe-Powershell-0f7a1bab). 62 | * This was written as a component to help simplify [migrating to the Paravirtual SCSI Controller](http://www.davidklee.net/2014/01/08/retrofit-a-vm-with-the-vmware-paravirtual-scsi-driver/). I've seen disks come up offline more often than not. 63 | * Accompanying [blog post](https://ramblingcookiemonster.wordpress.com/2015/02/24/remotely-brick-a-system/) (pretty much the stuff above, with more rambling) 64 | * TODO: More Pester tests 65 | * TODO: Refactor some of the DiskPart parsing. For example, PowerShell users might expect 'Yes' to be true, 'no' to be false 66 | * TODO: Add parameters to Get commands. For example, one should be able to get a specific disk, or limit output to 'list disk', rather than force 'detail disk'. 67 | -------------------------------------------------------------------------------- /Tests/PSDiskPart.Tests.ps1: -------------------------------------------------------------------------------- 1 | #handle PS2 2 | if(-not $PSScriptRoot) 3 | { 4 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 5 | } 6 | 7 | $PSVersion = $PSVersionTable.PSVersion.Major 8 | Import-Module -Force $PSScriptRoot\..\PSDiskPart 9 | 10 | 11 | #This is more appropriate for context, but we include PSVersion in the It blocks to differentiate in AppVeyor 12 | Describe "Invoke-DiskPartScript" { 13 | 14 | Context "Strict mode" { 15 | 16 | Set-StrictMode -Version latest 17 | 18 | It "Should list disks on a local system PS$PSVersion" { 19 | 20 | $OutString = Invoke-DiskPartScript -ComputerName $env:COMPUTERNAME -DiskPartText "list disk" -Raw 21 | $OutArray = ($OutString -split "`n") | Where-Object { $_ -match "[A-Za-z0-9]"} 22 | 23 | #Hopefully you have at least one disk. 24 | $OutArray.Count | Should BeGreaterThan 4 25 | 26 | #Is this different on other versions of Windows? Is there a better regex? 27 | $OutString | Should Match "\s*Disk ###\s*Status\s*Size\s*Free.*" 28 | } 29 | } 30 | } 31 | 32 | Describe "Get-DiskPartDisk" { 33 | 34 | Context "Strict mode" { 35 | 36 | Set-StrictMode -Version latest 37 | 38 | It "Should list disks on a local system PS$PSVersion" { 39 | 40 | $OutArray = @( Get-DiskPartDisk -ComputerName $env:COMPUTERNAME ) 41 | 42 | #Hopefully you have at least one disk. 43 | $OutArray.Count | Should BeGreaterThan 0 44 | } 45 | } 46 | } 47 | 48 | Describe "Get-DiskPartVolume" { 49 | 50 | Context "Strict mode" { 51 | 52 | Set-StrictMode -Version latest 53 | 54 | It "Should list volumes on a local system PS$PSVersion" { 55 | 56 | $OutArray = @( Get-DiskPartVolume -ComputerName $env:COMPUTERNAME ) 57 | 58 | #Hopefully you have at least one volume. 59 | $OutArray.Count | Should BeGreaterThan 0 60 | 61 | #Does it have a subset of props? 62 | $ActualProperties = $OutArray[0].PSObject.Properties | Select -ExpandProperty Name 63 | $ExpectedProperties = echo ComputerName VolumeNumber Letter Label 64 | $Comparison = Compare-Object -ReferenceObject $ActualProperties -DifferenceObject $ExpectedProperties 65 | 66 | ( $Comparison | Select -ExpandProperty SideIndicator ) -Contains "=>" | Should be $False 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/appveyor.pester.ps1: -------------------------------------------------------------------------------- 1 | # This script will invoke pester tests 2 | # It should invoke on PowerShell v2 and later 3 | # We serialize XML results and pull them in appveyor.yml 4 | 5 | #If Finalize is specified, we collect XML output, upload tests, and indicate build errors 6 | param([switch]$Finalize) 7 | 8 | #Initialize some variables, move to the project root 9 | $PSVersion = $PSVersionTable.PSVersion.Major 10 | $TestFile = "TestResultsPS$PSVersion.xml" 11 | $ProjectRoot = $ENV:APPVEYOR_BUILD_FOLDER 12 | Set-Location $ProjectRoot 13 | 14 | 15 | #Run a test with the current version of PowerShell 16 | if(-not $Finalize) 17 | { 18 | "`n`tSTATUS: Testing with PowerShell $PSVersion`n" 19 | 20 | Import-Module Pester 21 | 22 | Invoke-Pester -Path "$ProjectRoot\Tests" -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -PassThru | 23 | Export-Clixml -Path "$ProjectRoot\PesterResults$PSVersion.xml" 24 | } 25 | 26 | #If finalize is specified, check for failures and 27 | else 28 | { 29 | #Show status... 30 | $AllFiles = Get-ChildItem -Path $ProjectRoot\*Results*.xml | Select -ExpandProperty FullName 31 | "`n`tSTATUS: Finalizing results`n" 32 | "COLLATING FILES:`n$($AllFiles | Out-String)" 33 | 34 | #Upload results for test page 35 | Get-ChildItem -Path "$ProjectRoot\TestResultsPS*.xml" | Foreach-Object { 36 | 37 | $Address = "https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)" 38 | $Source = $_.FullName 39 | 40 | "UPLOADING FILES: $Address $Source" 41 | 42 | (New-Object 'System.Net.WebClient').UploadFile( $Address, $Source ) 43 | } 44 | 45 | #What failed? 46 | $Results = @( Get-ChildItem -Path "$ProjectRoot\PesterResults*.xml" | Import-Clixml ) 47 | 48 | $FailedCount = $Results | 49 | Select -ExpandProperty FailedCount | 50 | Measure-Object -Sum | 51 | Select -ExpandProperty Sum 52 | 53 | if ($FailedCount -gt 0) { 54 | 55 | $FailedItems = $Results | 56 | Select -ExpandProperty TestResult | 57 | Where {$_.Passed -notlike $True} 58 | 59 | "FAILED TESTS SUMMARY:`n" 60 | $FailedItems | ForEach-Object { 61 | $Test = $_ 62 | [pscustomobject]@{ 63 | Describe = $Test.Describe 64 | Context = $Test.Context 65 | Name = "It $($Test.Name)" 66 | Result = $Test.Result 67 | } 68 | } | 69 | Sort Describe, Context, Name, Result | 70 | Format-List 71 | 72 | throw "$FailedCount tests failed." 73 | } 74 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # See http://www.appveyor.com/docs/appveyor-yml for many more options 2 | 3 | # Skip on updates to the readme. 4 | # We can force this by adding [skip ci] or [ci skip] anywhere in commit message 5 | skip_commits: 6 | message: /updated readme.*/ 7 | 8 | install: 9 | - cinst pester 10 | 11 | build: false 12 | 13 | test_script: 14 | # Test with native PS version 15 | - ps: . .\Tests\appveyor.pester.ps1 16 | # Test with PS version 2 17 | - ps: powershell.exe -version 2.0 -executionpolicy bypass -noprofile -file .\Tests\appveyor.pester.ps1 18 | # Finalize pass - collect and upload results 19 | - ps: . .\Tests\appveyor.pester.ps1 -Finalize --------------------------------------------------------------------------------