├── .gitignore ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Convert-SqlServerInventoryClixmlToExcel.ps1 ├── Convert-WindowsInventoryClixmlToExcel.ps1 ├── Get-SqlServerInventoryToClixml.ps1 ├── Get-WindowsInventoryToClixml.ps1 ├── LICENSE.txt ├── Modules ├── LogHelper │ ├── LogHelper.psd1 │ └── LogHelper.psm1 ├── NetShell │ ├── NetShell.psd1 │ └── NetShell.psm1 ├── NetworkScan │ ├── NetworkScan.psd1 │ └── NetworkScan.psm1 ├── RDS-Manager │ └── RDS-Manager.psm1 ├── SqlServerDatabaseEngineInformation │ ├── SqlServerDatabaseEngineInformation.psd1 │ └── SqlServerDatabaseEngineInformation.psm1 ├── SqlServerInventory │ ├── SqlServerInventory.psd1 │ └── SqlServerInventory.psm1 ├── WindowsInventory │ ├── WindowsInventory.psd1 │ └── WindowsInventory.psm1 └── WindowsMachineInformation │ ├── WindowsMachineInformation.psd1 │ └── WindowsMachineInformation.psm1 ├── README.md ├── Tools └── SQL Inventory.ps1 ├── docs ├── SqlServerInventory Readme.txt ├── WindowsInventory Readme.txt ├── _config.yml └── index.md ├── renovate.json └── requirements.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_h.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *_wpftmp.csproj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Chutzpah Test files 92 | _Chutzpah* 93 | 94 | # Visual C++ cache files 95 | ipch/ 96 | *.aps 97 | *.ncb 98 | *.opendb 99 | *.opensdf 100 | *.sdf 101 | *.cachefile 102 | *.VC.db 103 | *.VC.VC.opendb 104 | 105 | # Visual Studio profiler 106 | *.psess 107 | *.vsp 108 | *.vspx 109 | *.sap 110 | 111 | # Visual Studio Trace Files 112 | *.e2e 113 | 114 | # TFS 2012 Local Workspace 115 | $tf/ 116 | 117 | # Guidance Automation Toolkit 118 | *.gpState 119 | 120 | # ReSharper is a .NET coding add-in 121 | _ReSharper*/ 122 | *.[Rr]e[Ss]harper 123 | *.DotSettings.user 124 | 125 | # JustCode is a .NET coding add-in 126 | .JustCode 127 | 128 | # TeamCity is a build add-in 129 | _TeamCity* 130 | 131 | # DotCover is a Code Coverage Tool 132 | *.dotCover 133 | 134 | # AxoCover is a Code Coverage Tool 135 | .axoCover/* 136 | !.axoCover/settings.json 137 | 138 | # Visual Studio code coverage results 139 | *.coverage 140 | *.coveragexml 141 | 142 | # NCrunch 143 | _NCrunch_* 144 | .*crunch*.local.xml 145 | nCrunchTemp_* 146 | 147 | # MightyMoose 148 | *.mm.* 149 | AutoTest.Net/ 150 | 151 | # Web workbench (sass) 152 | .sass-cache/ 153 | 154 | # Installshield output folder 155 | [Ee]xpress/ 156 | 157 | # DocProject is a documentation generator add-in 158 | DocProject/buildhelp/ 159 | DocProject/Help/*.HxT 160 | DocProject/Help/*.HxC 161 | DocProject/Help/*.hhc 162 | DocProject/Help/*.hhk 163 | DocProject/Help/*.hhp 164 | DocProject/Help/Html2 165 | DocProject/Help/html 166 | 167 | # Click-Once directory 168 | publish/ 169 | 170 | # Publish Web Output 171 | *.[Pp]ublish.xml 172 | *.azurePubxml 173 | # Note: Comment the next line if you want to checkin your web deploy settings, 174 | # but database connection strings (with potential passwords) will be unencrypted 175 | *.pubxml 176 | *.publishproj 177 | 178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 179 | # checkin your Azure Web App publish settings, but sensitive information contained 180 | # in these scripts will be unencrypted 181 | PublishScripts/ 182 | 183 | # NuGet Packages 184 | *.nupkg 185 | # The packages folder can be ignored because of Package Restore 186 | **/[Pp]ackages/* 187 | # except build/, which is used as an MSBuild target. 188 | !**/[Pp]ackages/build/ 189 | # Uncomment if necessary however generally it will be regenerated when needed 190 | #!**/[Pp]ackages/repositories.config 191 | # NuGet v3's project.json files produces more ignorable files 192 | *.nuget.props 193 | *.nuget.targets 194 | 195 | # Microsoft Azure Build Output 196 | csx/ 197 | *.build.csdef 198 | 199 | # Microsoft Azure Emulator 200 | ecf/ 201 | rcf/ 202 | 203 | # Windows Store app package directories and files 204 | AppPackages/ 205 | BundleArtifacts/ 206 | Package.StoreAssociation.xml 207 | _pkginfo.txt 208 | *.appx 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !?*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | orleans.codegen.cs 226 | 227 | # Including strong name files can present a security risk 228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 229 | #*.snk 230 | 231 | # Since there are multiple workflows, uncomment next line to ignore bower_components 232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 233 | #bower_components/ 234 | # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true 235 | **/wwwroot/lib/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | 261 | # Microsoft Fakes 262 | FakesAssemblies/ 263 | 264 | # GhostDoc plugin setting file 265 | *.GhostDoc.xml 266 | 267 | # Node.js Tools for Visual Studio 268 | .ntvs_analysis.dat 269 | node_modules/ 270 | 271 | # Visual Studio 6 build log 272 | *.plg 273 | 274 | # Visual Studio 6 workspace options file 275 | *.opt 276 | 277 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 278 | *.vbw 279 | 280 | # Visual Studio LightSwitch build output 281 | **/*.HTMLClient/GeneratedArtifacts 282 | **/*.DesktopClient/GeneratedArtifacts 283 | **/*.DesktopClient/ModelManifest.xml 284 | **/*.Server/GeneratedArtifacts 285 | **/*.Server/ModelManifest.xml 286 | _Pvt_Extensions 287 | 288 | # Paket dependency manager 289 | .paket/paket.exe 290 | paket-files/ 291 | 292 | # FAKE - F# Make 293 | .fake/ 294 | 295 | # JetBrains Rider 296 | .idea/ 297 | *.sln.iml 298 | 299 | # CodeRush personal settings 300 | .cr/personal 301 | 302 | # Python Tools for Visual Studio (PTVS) 303 | __pycache__/ 304 | *.pyc 305 | 306 | # Cake - Uncomment if you are using it 307 | # tools/** 308 | # !tools/packages.config 309 | 310 | # Tabs Studio 311 | *.tss 312 | 313 | # Telerik's JustMock configuration file 314 | *.jmconfig 315 | 316 | # BizTalk build output 317 | *.btp.cs 318 | *.btm.cs 319 | *.odx.cs 320 | *.xsd.cs 321 | 322 | # OpenCover UI analysis results 323 | OpenCover/ 324 | 325 | # Azure Stream Analytics local run output 326 | ASALocalRun/ 327 | 328 | # MSBuild Binary and Structured Log 329 | *.binlog 330 | 331 | # NVidia Nsight GPU debugger configuration file 332 | *.nvuser 333 | 334 | # MFractors (Xamarin productivity tool) working folder 335 | .mfractor/ 336 | 337 | # Local History for Visual Studio 338 | .localhistory/ 339 | 340 | # BeatPulse healthcheck temp database 341 | healthchecksdb -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "PowerShell", 6 | "request": "launch", 7 | "name": "PowerShell Launch Current File", 8 | "script": "${file}", 9 | "args": [], 10 | "cwd": "${file}" 11 | }, 12 | { 13 | "type": "PowerShell", 14 | "request": "launch", 15 | "name": "PowerShell Launch Current File in Temporary Console", 16 | "script": "${file}", 17 | "args": [], 18 | "cwd": "${file}", 19 | "createTemporaryIntegratedConsole": true 20 | }, 21 | { 22 | "type": "PowerShell", 23 | "request": "launch", 24 | "name": "PowerShell Launch Current File w/Args Prompt", 25 | "script": "${file}", 26 | "args": [ 27 | "${command:SpecifyScriptArgs}" 28 | ], 29 | "cwd": "${file}" 30 | }, 31 | { 32 | "type": "PowerShell", 33 | "request": "attach", 34 | "name": "PowerShell Attach to Host Process", 35 | "processId": "${command:PickPSHostProcess}", 36 | "runspaceId": 1 37 | }, 38 | { 39 | "type": "PowerShell", 40 | "request": "launch", 41 | "name": "PowerShell Interactive Session", 42 | "cwd": "${workspaceRoot}" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team (only SheepReaper at the time of posting) at bgonza868@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### What do I need to know to help? 2 | 3 | If you are looking to help to with a code contribution, our project uses PowerShell (and the .NET Framework). If you don't feel ready to make a code contribution yet, no problem! You can also look for documentation issues (documentation issues are tracked just like code issues) . 4 | 5 | If you are interested in making a code contribution and would like to learn more about the technologies that we use, check out the list below. 6 | 7 | Include bulleted list of 8 | resources (tutorials, videos, books) that new contributors 9 | can use to learn what they need to know to contribute to your project 10 | 11 | ### How do I make a contribution? 12 | 13 | #### Never made an open source contribution before? Wondering how contributions work in the in our project? Here's a quick rundown! 14 | 1. Find an issue that you are interested in addressing or a feature that you would like to add. 15 | 1. Fork the repository associated with the issue to your local GitHub organization. This means that you will have a copy of the repository under your-GitHub-username/SQLPowerDoc. 16 | 1. Clone the repository to your local machine using: 17 | git clone https://github.com/bryan5989/SQLPowerDoc.git 18 | 1. Create a new branch for your fix using: 19 | git checkout -b branch-name-here 20 | 1. Make the appropriate changes for the issue you are trying to address or the feature that you want to add. 21 | 1. Use: 22 | git add insert-paths-of-changed-files-here 23 | to add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index. 24 | 1. Use: 25 | git commit -m "Insert a short message of the changes made here" 26 | to store the contents of the index with a descriptive message. 27 | 1. Push the changes to the remote repository using: 28 | git push origin branch-name-here 29 | 1. Submit a pull request to the upstream repository. 30 | 1. Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like so "Added more log outputting to resolve #4352". 31 | 1. In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainer. It's OK if your pull request is not perfect (no pull request is), the reviewer will be able to help you fix any problems and improve it! 32 | 1. Wait for the pull request to be reviewed by a maintainer. 33 | 1. Make changes to the pull request if the reviewing maintainer recommends them. 34 | 1. Celebrate your success after your pull request is merged! 35 | 36 | ### Where can I go for help? 37 | 38 | At this time, there is only the documentation wiki . I plan to update this with other means of contact if the project becomes larger. 39 | 40 | ### What does the Code of Conduct mean for me? 41 | 42 | Our Code of Conduct means that you are responsible for treating everyone on the project with respect and courtesy regardless of their identity. If you are the victim of any inappropriate behavior or comments as described in our Code of Conduct, we are here for you and will do the best to ensure that the abuser is reprimanded appropriately, per our code. 43 | -------------------------------------------------------------------------------- /Convert-SqlServerInventoryClixmlToExcel.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Writes Excel files containing the Database Engine and Windows Operating System information from a SQL Server Inventory file created by Get-SqlServerInventoryToClixml.ps1. 4 | 5 | .DESCRIPTION 6 | This script loads a SQL Server Inventory file created by Get-SqlServerInventoryToClixml.ps1 and calls the Export-SqlDatabaseEngineInventoryToExcel and Export-SqlWindowsInventoryToExcel functions in the SqlServerInventory module to write Excel files containing the Database Engine and Windows Operating System information from the inventory. 7 | 8 | Microsoft Excel 2007 or higher must be installed in order to write the Excel file. 9 | 10 | .PARAMETER FromPath 11 | The literal path to the XML file created by Get-SqlServerInventoryToClixml.ps1. 12 | 13 | .PARAMETER ToDirectoryPath 14 | Specifies the literal path to the directory where the Excel workbooks will be written. This path must exist prior to executing this script. 15 | 16 | If not specified then ToDirectoryPath defaults to the same directory specified by the FromPath paramter. 17 | 18 | Assuming the XML file specified in FromPath is named "SQL Server Inventory.xml" then: 19 | The Database Engine configuration details will be written to "SQL Server Inventory - Database Engine Config.xlsx" 20 | The Database Engine Database Objects details will be written to "SQL Server Inventory - Database Engine Db Objects.xlsx" 21 | The Database Engine configuration assessment will be written to "SQL Server Inventory - Database Engine Assessment.xlsx" 22 | The Windows Operating System details will be written to "SQL Server Inventory - Windows.xlsx" 23 | 24 | .PARAMETER ColorTheme 25 | An Office Theme Color to apply to each worksheet. If not specified or if an unknown theme color is provided the default "Office" theme colors will be used. 26 | 27 | Office 2013 theme colors include: Aspect, Blue Green, Blue II, Blue Warm, Blue, Grayscale, Green Yellow, Green, Marquee, Median, Office, Office 2007 - 2010, Orange Red, Orange, Paper, Red Orange, Red Violet, Red, Slipstream, Violet II, Violet, Yellow Orange, Yellow 28 | 29 | Office 2010 theme colors include: Adjacency, Angles, Apex, Apothecary, Aspect, Austin, Black Tie, Civic, Clarity, Composite, Concourse, Couture, Elemental, Equity, Essential, Executive, Flow, Foundry, Grayscale, Grid, Hardcover, Horizon, Median, Metro, Module, Newsprint, Office, Opulent, Oriel, Origin, Paper, Perspective, Pushpin, Slipstream, Solstice, Technic, Thatch, Trek, Urban, Verve, Waveform 30 | 31 | Office 2007 theme colors include: Apex, Aspect, Civic, Concourse, Equity, Flow, Foundry, Grayscale, Median, Metro, Module, Office, Opulent, Oriel, Origin, Paper, Solstice, Technic, Trek, Urban, Verve 32 | 33 | .PARAMETER ColorScheme 34 | The color theme to apply to each worksheet. Valid values are "Light", "Medium", and "Dark". 35 | 36 | If not specified then "Medium" is used as the default value . 37 | 38 | .PARAMETER LoggingPreference 39 | Specifies the logging verbosity to use when writing log entries. 40 | 41 | Valid values include: None, Standard, Verbose, and Debug. 42 | 43 | The default value is "None" 44 | 45 | .PARAMETER LogPath 46 | A literal path to a log file to write details about what this script is doing. The filename does not need to exist prior to executing this script but the specified directory does. 47 | 48 | If a LoggingPreference other than None is specified and this parameter is not specified then the file is named "SQL Server Inventory - [Year][Month][Day][Hour][Minute].log" and is written to the same directory specified by the ToDirectoryPath paramter. 49 | 50 | .EXAMPLE 51 | .\Convert-SqlServerInventoryClixmlToExcel.ps1 -FromPath "C:\Inventory\SQL Server Inventory.xml" 52 | 53 | Description 54 | ----------- 55 | Writes Excel files for the Database Engine and Windows Operating System information contained in "C:\Inventory\SQL Server Inventory.xml" to "C:\Inventory\SQL Server - Database Engine.xlsx" and "C:\Inventory\SQL Server - Windows.xlsx", respectively. 56 | 57 | The Office color theme and Medium color scheme will be used by default. 58 | 59 | .EXAMPLE 60 | .\Convert-SqlServerInventoryClixmlToExcel.ps1 -FromPath "C:\Inventory\SQL Server Inventory.xml" -ColorTheme Blue -ColorScheme Dark 61 | 62 | Description 63 | ----------- 64 | Writes Excel files for the Database Engine and Windows Operating System information contained in "C:\Inventory\SQL Server Inventory.xml" to "C:\Inventory\SQL Server - Database Engine.xlsx" and "C:\Inventory\SQL Server - Windows.xlsx", respectively. 65 | 66 | The Blue color theme and Dark color scheme will be used. 67 | 68 | 69 | .NOTES 70 | Blue and Green are nice looking Color Themes for Office 2013 71 | 72 | Waveform is a nice looking Color Theme for Office 2010 73 | 74 | .LINK 75 | Get-SqlServerInventory 76 | 77 | #> 78 | [cmdletBinding(SupportsShouldProcess=$false)] 79 | param( 80 | [Parameter(Mandatory=$true)] 81 | [alias('From')] 82 | [ValidateNotNullOrEmpty()] 83 | [string] 84 | $FromPath 85 | , 86 | [Parameter(Mandatory=$false)] 87 | [alias('To')] 88 | [ValidateNotNullOrEmpty()] 89 | [string] 90 | $ToDirectoryPath = ([System.IO.Path]::GetDirectoryName($FromPath)) 91 | , 92 | [Parameter(Mandatory=$false)] 93 | [alias('LogLevel')] 94 | [ValidateSet('None','Standard','Verbose','Debug')] 95 | [string] 96 | $LoggingPreference = 'none' 97 | , 98 | [Parameter(Mandatory=$false)] 99 | [alias('Log')] 100 | [ValidateNotNullOrEmpty()] 101 | [string] 102 | $LogPath = (Join-Path -Path $ToDirectoryPath -ChildPath ("SQL Server Inventory - " + (Get-Date -Format "yyyy-MM-dd-HH-mm") + ".log")) 103 | , 104 | [Parameter(Mandatory=$false)] 105 | [alias('Theme')] 106 | [string] 107 | $ColorTheme = 'Office' 108 | , 109 | [Parameter(Mandatory=$false)] 110 | [ValidateSet('Dark','Light','Medium')] 111 | [string] 112 | $ColorScheme = 'Medium' 113 | ) 114 | 115 | ###################### 116 | # FUNCTIONS 117 | ###################### 118 | 119 | function Write-LogMessage { 120 | [CmdletBinding()] 121 | param( 122 | [Parameter(Position=0, Mandatory=$true)] 123 | [ValidateNotNullOrEmpty()] 124 | [System.String] 125 | $Message 126 | , 127 | [Parameter(Position=1, Mandatory=$true)] 128 | [alias('level')] 129 | [ValidateSet('information','verbose','debug','error','warning')] 130 | [System.String] 131 | $MessageLevel 132 | ) 133 | try { 134 | if ((Test-Path -Path 'function:Write-Log') -eq $true) { 135 | Write-Log -Message $Message -MessageLevel $MessageLevel 136 | } else { 137 | Write-Host $Message 138 | } 139 | } 140 | catch { 141 | throw 142 | } 143 | } 144 | 145 | 146 | ###################### 147 | # VARIABLES 148 | ###################### 149 | $Inventory = $null 150 | $ProgressId = Get-Random 151 | $ProgressActivity = 'Convert-SqlServerInventoryClixmlToExcel' 152 | $ProgressStatus = $null 153 | 154 | 155 | ###################### 156 | # BEGIN SCRIPT 157 | ###################### 158 | 159 | # Import Modules that we need 160 | Import-Module -Name LogHelper, SqlServerInventory 161 | 162 | # Set logging variables 163 | Set-LogFile -Path $LogPath 164 | Set-LoggingPreference -Preference $LoggingPreference 165 | 166 | $ProgressStatus = "Starting Script: $($MyInvocation.MyCommand.Path)" 167 | Write-LogMessage -Message $ProgressStatus -MessageLevel Information 168 | Write-Progress -Activity $ProgressActivity -PercentComplete 0 -Status $ProgressStatus -Id $ProgressId 169 | 170 | $ProgressStatus = "Loading inventory from '$FromPath'" 171 | Write-LogMessage -Message $ProgressStatus -MessageLevel Information 172 | Write-Progress -Activity $ProgressActivity -PercentComplete 25 -Status $ProgressStatus -Id $ProgressId 173 | 174 | $Inventory = Import-SqlServerInventoryFromGzClixml -Path $FromPath 175 | 176 | $ProgressStatus = "Writing inventory to Excel (Go get a coffee, this can take a few minutes...)" 177 | Write-LogMessage -Message $ProgressStatus -MessageLevel Information 178 | Write-Progress -Activity $ProgressActivity -PercentComplete 75 -Status $ProgressStatus -Id $ProgressId 179 | 180 | Export-SqlServerInventoryToExcel ` 181 | -SqlServerInventory $Inventory ` 182 | -DirectoryPath $ToDirectoryPath ` 183 | -BaseFilename $([System.IO.Path]::GetFileNameWithoutExtension($FromPath)) ` 184 | -ColorTheme $ColorTheme ` 185 | -ColorScheme $ColorScheme ` 186 | -ParentProgressId $ProgressId 187 | 188 | 189 | $ProgressStatus = "End Script: $($MyInvocation.MyCommand.Path)" 190 | Write-LogMessage -Message $ProgressStatus -MessageLevel Information 191 | Write-Progress -Activity $ProgressActivity -PercentComplete 100 -Status $ProgressStatus -Id $ProgressId -Completed 192 | 193 | # Remove Variables 194 | Remove-Variable -Name Inventory, ProgressId, ProgressActivity, ProgressStatus 195 | 196 | # Remove Modules 197 | Remove-Module -Name SqlServerInventory, LogHelper 198 | 199 | # Call garbage collector 200 | [System.GC]::Collect() 201 | 202 | -------------------------------------------------------------------------------- /Convert-WindowsInventoryClixmlToExcel.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Convert-WindowsInventoryClixmlToExcel.ps1 -------------------------------------------------------------------------------- /Get-SqlServerInventoryToClixml.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Collects comprehensive information about SQL Server instances and their underlying Windows Operating System and saves the result to a file. 4 | 5 | .DESCRIPTION 6 | This script leverages the NetworkScan, SqlServerInventory, and WindowsInventory modules to scan for and collect comprehensive information about SQL Server instances and their underlying Windows Operating System and save the results to a file in PowerShell's CLIXML format. 7 | 8 | This script can find, verify, and collect information by Computer Name, Subnet Scan, or Active Directory DNS query. 9 | 10 | This script collects information from SQL Server 2000 or higher running on Windows 2000 or higher and Windows Azure SQL Database (if using SMO 2008 or higher). 11 | 12 | This SqlServerInventory and WindowsInventory modules use SQL Server Shared Management Objects (SMO) and Windows Management Instrumentation (WMI) to collect information. As such this script works best when using a version of SMO that matches or is higher than the highest version of each SQL Server instance information is being collected from. 13 | 14 | The latest version of SMO can be downloaded from http://www.microsoft.com/en-us/download/details.aspx?id=29065 15 | Note that SMO also requires the Microsoft SQL Server System CLR Types which can be downloaded from the same page 16 | 17 | .PARAMETER DnsServer 18 | 'Automatic', or the Name or IP address of an Active Directory DNS server to query for a list of hosts to inventory (if there is an instance of SQL Server installed). 19 | 20 | When 'Automatic' is specified the function will use WMI queries to discover the current computer's DNS server(s) to query. 21 | 22 | .PARAMETER DnsDomain 23 | 'Automatic' or the Active Directory domain name to use when querying DNS for a list of hosts. 24 | 25 | When 'Automatic' is specified the function will use the current computer's AD domain. 26 | 27 | 'Automatic' will be used by default if DnsServer is specified but DnsDomain is not provided. 28 | 29 | .PARAMETER Subnet 30 | 'Automatic' or a comma delimited list of subnets (in CIDR notation) to scan for SQL Server instances. 31 | 32 | When 'Automatic' is specified the function will use the current computer's IP configuration to determine subnets to scan. 33 | 34 | A quick refresher on CIDR notation: 35 | 36 | BITS SUBNET MASK USABLE HOSTS PER SUBNET 37 | ---- --------------- ----------------------- 38 | /20 255.255.240.0 4094 39 | /21 255.255.248.0 2046 40 | /22 255.255.252.0 1022 41 | /23 255.255.254.0 510 42 | /24 255.255.255.0 254 43 | /25 255.255.255.128 126 44 | /26 255.255.255.192 62 45 | /27 255.255.255.224 30 46 | /28 255.255.255.240 14 47 | /29 255.255.255.248 6 48 | /30 255.255.255.252 2 49 | /32 255.255.255.255 1 50 | 51 | .PARAMETER ComputerName 52 | A comma delimited list of computer names to inventory. 53 | 54 | .PARAMETER ExcludeSubnet 55 | A comma delimited list of subnets (in CIDR notation) to exclude when testing for connectivity. 56 | 57 | .PARAMETER LimitSubnet 58 | A comma delimited list of subnets (in CIDR notation) to limit the scope of connectivity tests. Only hosts with IP Addresses that fall within the specified subnet(s) will be included in the results. 59 | 60 | .PARAMETER ExcludeComputerName 61 | A comma delimited list of computer names to exclude when testing for connectivity. Wildcards are accepted. 62 | 63 | An attempt will be made to resolve the IP Address(es) for each computer in this list and those addresses will also be used when determining if a host should be included or excluded when testing for connectivity. 64 | 65 | .PARAMETER Username 66 | SQL Server username to use when connecting to instances. 67 | 68 | Windows authentication will be used to connect if this parameter is not provided. 69 | 70 | .PARAMETER Password 71 | SQL Server password to use when connecting to instances. 72 | 73 | .PARAMETER MaxConcurrencyThrottle 74 | Number between 1-100 to indicate how many instances to collect information from concurrently. 75 | 76 | If not provided then the number of logical CPUs present to your session will be used. 77 | 78 | .PARAMETER PrivateOnly 79 | Restrict inventory to instances on private class A, B, or C IP addresses 80 | 81 | .PARAMETER DirectoryPath 82 | Specifies the literal path to the directory where the inventory file and log file will be written. 83 | 84 | If not specified then the script defaults to your "My Documents" folder. 85 | 86 | .PARAMETER LoggingPreference 87 | Specifies the logging verbosity to use when writing log entries. 88 | 89 | Valid values include: None, Standard, Verbose, and Debug. 90 | 91 | The default value is "None" 92 | 93 | .PARAMETER Zip 94 | Combine the Inventory and Log files into a single compressed ZIP file. This is useful for transferring the output of an inventory to another machine for further analysis. 95 | 96 | .PARAMETER IncludeDatabaseObjectPermissions 97 | Includes database object level permissions (System object permissions included only if -IncludeDatabaseSystemObjects is also provided) 98 | 99 | .PARAMETER IncludeDatabaseObjectInformation 100 | Includes database object information (but does not include system objects) 101 | 102 | .PARAMETER IncludeDatabaseSystemObjects 103 | Include system objects when retrieving database object information. 104 | 105 | This has no effect if neither -IncludeDatabaseObjectInformation nor -IncludeDatabaseObjectPermissions are specified. 106 | 107 | .EXAMPLE 108 | .\Get-SqlServerInventoryToClixml.ps1 -DNSServer automatic -DNSDomain automatic -PrivateOnly 109 | 110 | Description 111 | ----------- 112 | Collect an inventory by querying Active Directory for a list of hosts to scan for SQL Server instances. The list of hosts will be restricted to private IP addresses only. 113 | 114 | Windows Authentication will be used to connect to each instance. 115 | 116 | Database objects will NOT be included in the results. 117 | 118 | The Inventory file will be written to your "My Documents" folder. 119 | 120 | No log file will be written. 121 | 122 | .EXAMPLE 123 | .\Get-SqlServerInventoryToClixml.ps1 -Subnet 172.20.40.0/28 -Username sa -Password BetterNotBeBlank 124 | 125 | Description 126 | ----------- 127 | Collect an inventory by scanning all hosts in the subnet 172.20.40.0/28 for SQL Server instances. 128 | 129 | SQL authentication (username = "sa", password = "BetterNotBeBlank") will be used to connect to the instance. 130 | 131 | Database objects will NOT be included in the results. 132 | 133 | The Inventory file will be written to your "My Documents" folder. 134 | 135 | No log file will be written. 136 | 137 | .EXAMPLE 138 | .\Get-SqlServerInventoryToClixml.ps1 -Computername Server1,Server2,Server3 -LoggingPreference Standard 139 | 140 | Description 141 | ----------- 142 | Collect an inventory by scanning Server1, Server2, and Server3 for SQL Server instances. 143 | 144 | Windows Authentication will be used to connect to the instance. 145 | 146 | Database objects will NOT be included in the results. 147 | 148 | The Inventory file will be written to your "My Documents" folder. 149 | 150 | Standard logging will be used. 151 | 152 | .EXAMPLE 153 | .\Get-SqlServerInventoryToClixml.ps1 -Computername $env:COMPUTERNAME -IncludeDatabaseObjectInformation -LoggingPreference Verbose 154 | 155 | Description 156 | ----------- 157 | Collect an inventory by scanning the local machine for SQL Server instances. 158 | 159 | Windows Authentication will be used to connect to the instance. 160 | 161 | Database objects (EXCLUDING system objects) will be included in the results. 162 | 163 | The Inventory file will be written to your "My Documents" folder. 164 | 165 | Verbose logging will be used. 166 | 167 | .EXAMPLE 168 | .\Get-SqlServerInventoryToClixml.ps1 -Computername $env:COMPUTERNAME -IncludeDatabaseObjectInformation -IncludeDatabaseSystemObjects 169 | 170 | Description 171 | ----------- 172 | Collect an inventory by scanning the local machine for SQL Server instances. 173 | 174 | Windows Authentication will be used to connect to the instance. 175 | 176 | Database objects (INCLUDING system objects) will be included in the results. 177 | 178 | 179 | .OUTPUTS 180 | System.Management.Automation.PSObject 181 | 182 | .NOTES 183 | 184 | .LINK 185 | .\Convert-SqlServerInventoryClixmlToExcel.ps1 186 | 187 | #> 188 | [cmdletBinding(SupportsShouldProcess=$True, DefaultParametersetName='computername')] 189 | param( 190 | [Parameter(Mandatory=$true, ParameterSetName='dns', HelpMessage='DNS Server(s)')] 191 | [alias('dns')] 192 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^auto$|^automatic$')] 193 | [string[]] 194 | $DnsServer = 'automatic' 195 | , 196 | [Parameter(Mandatory=$false, ParameterSetName='dns', HelpMessage='DNS Domain Name')] 197 | [alias('domain')] 198 | [string] 199 | $DnsDomain = 'automatic' 200 | , 201 | [Parameter(Mandatory=$true, ParameterSetName='subnet', HelpMessage='Subnet (in CIDR notation)')] 202 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$|^auto$|^automatic$')] 203 | [string[]] 204 | $Subnet = 'automatic' 205 | , 206 | [Parameter(Mandatory=$true, ParameterSetName='computername', HelpMessage='Computer Name(s)')] 207 | [alias('computer')] 208 | [string[]] 209 | $ComputerName 210 | , 211 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 212 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 213 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 214 | [string[]] 215 | $ExcludeSubnet 216 | , 217 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 218 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 219 | [string[]] 220 | $LimitSubnet 221 | , 222 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 223 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 224 | [string[]] 225 | $ExcludeComputerName 226 | , 227 | [Parameter(Mandatory=$false)] 228 | [System.String] 229 | $Username = $null 230 | , 231 | [Parameter(Mandatory=$false)] 232 | [System.String] 233 | $Password = $null 234 | , 235 | [Parameter(Mandatory=$false)] 236 | [ValidateRange(1,100)] 237 | [byte] 238 | $MaxConcurrencyThrottle = $env:NUMBER_OF_PROCESSORS 239 | , 240 | [Parameter(Mandatory=$false)] 241 | [switch] 242 | $PrivateOnly = $false 243 | , 244 | [Parameter(Mandatory=$false)] 245 | [alias('Directory','Path')] 246 | [ValidateNotNullOrEmpty()] 247 | [string] 248 | $DirectoryPath = ([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)) 249 | , 250 | [Parameter(Mandatory=$false)] 251 | [alias('LogLevel')] 252 | [ValidateSet('none','standard','verbose','debug')] 253 | [string] 254 | $LoggingPreference = 'none' 255 | , 256 | [Parameter(Mandatory=$false)] 257 | [switch] 258 | $Zip = $false 259 | , 260 | [Parameter(Mandatory=$false)] 261 | [switch] 262 | $IncludeDatabaseObjectPermissions = $false 263 | , 264 | [Parameter(Mandatory=$false)] 265 | [alias('IncludeDbObjects','IncludeDatabaseObjects')] 266 | [switch] 267 | $IncludeDatabaseObjectInformation = $false 268 | , 269 | [Parameter(Mandatory=$false)] 270 | [switch] 271 | $IncludeDatabaseSystemObjects = $false 272 | ) 273 | 274 | 275 | ###################### 276 | # FUNCTIONS 277 | ###################### 278 | 279 | function Write-LogMessage { 280 | [CmdletBinding()] 281 | param( 282 | [Parameter(Position=0, Mandatory=$true)] 283 | [ValidateNotNullOrEmpty()] 284 | [System.String] 285 | $Message 286 | , 287 | [Parameter(Position=1, Mandatory=$true)] 288 | [alias('level')] 289 | [ValidateSet('information','verbose','debug','error','warning')] 290 | [System.String] 291 | $MessageLevel 292 | ) 293 | try { 294 | if ((Test-Path -Path 'function:Write-Log') -eq $true) { 295 | Write-Log -Message $Message -MessageLevel $MessageLevel 296 | } else { 297 | Write-Host $Message 298 | } 299 | } 300 | catch { 301 | Throw 302 | } 303 | } 304 | 305 | 306 | function Test-FileIsOpen { 307 | [CmdletBinding()] 308 | [OutputType([System.Boolean])] 309 | param( 310 | [Parameter(Position=0, Mandatory=$true)] 311 | [ValidateNotNullOrEmpty()] 312 | [System.String] 313 | $Path 314 | ) 315 | process { 316 | $FileIsOpen = $false 317 | $Filestream = $null 318 | 319 | try { 320 | $Filestream = [System.IO.File]::Open($ZipFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None) 321 | $Filestream.Close() 322 | Write-Output $false 323 | } 324 | catch { 325 | Write-Output $true 326 | } 327 | } 328 | } 329 | 330 | 331 | ###################### 332 | # VARIABLES 333 | ###################### 334 | 335 | $Inventory = $null 336 | $ParameterHash = $null 337 | [String]$ZipFilePath = $null 338 | $ZipFile = $null 339 | 340 | $BasePath = (Join-Path -Path $DirectoryPath -ChildPath ('SQL Server Inventory - ' + (Get-Date -Format 'yyyy-MM-dd-HH-mm'))) 341 | $CliXmlPath = [System.IO.Path]::ChangeExtension($BasePath, 'xml.gz') 342 | $LogPath = [System.IO.Path]::ChangeExtension($BasePath, 'log') 343 | $ZipFilePath = [System.IO.Path]::ChangeExtension($BasePath,'zip') 344 | 345 | # Fallback in case value isn't supplied or somehow missing from the environment variables 346 | if (-not $MaxConcurrencyThrottle) { $MaxConcurrencyThrottle = 1 } 347 | 348 | ###################### 349 | # BEGIN SCRIPT 350 | ###################### 351 | 352 | # Import Modules that we need 353 | Import-Module -Name LogHelper, SqlServerInventory 354 | 355 | 356 | # Set logging variables 357 | Set-LogFile -Path $LogPath 358 | Set-LoggingPreference -Preference $LoggingPreference 359 | 360 | Write-LogMessage -Message "Starting Script: $($MyInvocation.MyCommand.Path)" -MessageLevel Information 361 | <# 362 | $PsCmdlet.MyInvocation.MyCommand.Parameters.Values | Where-Object { $_.ParameterType.Name -ine 'actionpreference' } | ForEach-Object { 363 | $Param = Invoke-Expression $('[' + $(if ($_.ParameterType.Name -ieq 'SwitchParameter') { 'Boolean' } else { $_.ParameterType.Name }) + ']$' + $_.Name) 364 | Write-LogMessage -Message "`t-$($_.Name): $Param" -MessageLevel Information 365 | } 366 | #> 367 | 368 | # Build inventory collection command parameters 369 | $ParameterHash = @{ 370 | MaxConcurrencyThrottle = $MaxConcurrencyThrottle 371 | PrivateOnly = $PrivateOnly 372 | IncludeDatabaseObjectPermissions = $IncludeDatabaseObjectPermissions 373 | IncludeDatabaseObjectInformation = $IncludeDatabaseObjectInformation 374 | IncludeDatabaseSystemObjects = $IncludeDatabaseSystemObjects 375 | Username = $Username 376 | Password = $Password 377 | } 378 | 379 | switch ($PsCmdlet.ParameterSetName) { 380 | 'dns' { 381 | $ParameterHash.Add('DnsServer',$DnsServer) 382 | $ParameterHash.Add('DnsDomain',$DnsDomain) 383 | if ($ExcludeSubnet) { $ParameterHash.Add('ExcludeSubnet',$ExcludeSubnet) } 384 | if ($LimitSubnet) { $ParameterHash.Add('IncludeSubnet',$LimitSubnet) } 385 | if ($ExcludeComputerName) { $ParameterHash.Add('ExcludeComputerName',$ExcludeComputerName) } 386 | } 387 | 'subnet' { 388 | $ParameterHash.Add('Subnet',$Subnet) 389 | if ($ExcludeSubnet) { $ParameterHash.Add('ExcludeSubnet',$ExcludeSubnet) } 390 | if ($ExcludeComputerName) { $ParameterHash.Add('ExcludeComputerName',$ExcludeComputerName) } 391 | } 392 | 'computername' { 393 | $ParameterHash.Add('ComputerName',$ComputerName) 394 | } 395 | } 396 | 397 | 398 | # Collect inventory and export results to Excel (if there are results in the inventory collection) 399 | $Inventory = Get-SqlServerInventory @ParameterHash 400 | 401 | Write-LogMessage -Message 'Writing Inventory to disk' -MessageLevel Information 402 | 403 | if ($Inventory.DatabaseServerScanSuccessCount -gt 0) { 404 | #Compress-SqlServerInventory -SqlServerInventory ([REF]$Inventory) 405 | #$Inventory | Export-Clixml -Path $CliXmlPath -Force -Depth 100 -Encoding UTF8 406 | 407 | $Inventory | Export-SqlServerInventoryToGzClixml -Path $CliXmlPath 408 | 409 | #Compress-SqlServerInventory -SqlServerInventory ([REF]$Inventory) | Export-Clixml -Path $CliXmlPath -Force -Depth 100 -Encoding UTF8 410 | #$_ | Export-Clixml -Path $CliXmlPath -Force -Depth 100 -Encoding UTF8 411 | } else { 412 | Write-LogMessage -Message 'No machines found!' -MessageLevel Warning 413 | } 414 | 415 | Write-LogMessage -Message "End Script: $($MyInvocation.MyCommand.Path)" -MessageLevel Information 416 | 417 | 418 | # Try to create ZIP archive if the option was specified and a log or CliXml file was created 419 | if ( 420 | $Zip -eq $true -and 421 | ( 422 | $(Test-Path -Path $LogPath) -or 423 | $(Test-Path -Path $CliXmlPath) 424 | ) 425 | ) { 426 | 427 | # Create the zip file; if it already exists write a message to the console and skip this part 428 | if ($(Test-Path -Path $ZipFilePath) -ne $true) { 429 | 430 | Set-Content -Path $ZipFilePath -Value ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18)) 431 | $ZipFile = (New-Object -ComObject Shell.Application).NameSpace($ZipFilePath) 432 | 433 | # Add log file if it exists 434 | if (($LoggingPreference -ine 'none') -and (Test-Path -Path $LogPath)) { 435 | $ZipFile.CopyHere($LogPath) 436 | 437 | Start-Sleep -Milliseconds 500 438 | while (Test-FileIsOpen -Path $ZipFilePath) { 439 | Start-Sleep -Seconds 1 440 | } 441 | } 442 | 443 | # Add CliXml file if it exists 444 | if (Test-Path -Path $CliXmlPath) { 445 | $ZipFile.CopyHere($CliXmlPath) 446 | 447 | Start-Sleep -Milliseconds 500 448 | while (Test-FileIsOpen -Path $ZipFilePath) { 449 | Start-Sleep -Seconds 1 450 | } 451 | } 452 | 453 | # Remove reference to zip file 454 | $ZipFile = $null 455 | 456 | } else { 457 | Write-LogMessage -Message "Unable to compress files - '$ZipFilePath' already exists" -MessageLevel Error 458 | } 459 | } 460 | 461 | 462 | # Remove Variables 463 | Remove-Variable -Name Inventory, ParameterHash, ZipFilePath, ZipFile, BasePath, CliXmlPath, LogPath 464 | 465 | # Remove Modules 466 | Remove-Module -Name SqlServerInventory, LogHelper 467 | 468 | # Call garbage collector 469 | [System.GC]::Collect() 470 | -------------------------------------------------------------------------------- /Get-WindowsInventoryToClixml.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Collects comprehensive information about hosts running Microsoft Windows and saves the result to a file. 4 | 5 | .DESCRIPTION 6 | This script leverages the NetworkScan and WindowsInventory modules along with Windows Management Instrumentation (WMI) to scan for and collect comprehensive information about hosts running a Windows Operating System and save the results to a file in PowerShell's CLIXML format. 7 | 8 | This script can can find, verify, and collect information by Computer Name, Subnet Scan, or Active Directory DNS query. 9 | 10 | This script collects information from Windows 2000 or higher. 11 | 12 | .PARAMETER DnsServer 13 | 'Automatic', or the Name or IP address of an Active Directory DNS server to query for a list of hosts to inventory. 14 | 15 | When 'Automatic' is specified the function will use WMI queries to discover the current computer's DNS server(s) to query. 16 | 17 | .PARAMETER DnsDomain 18 | 'Automatic' or the Active Directory domain name to use when querying DNS for a list of hosts. 19 | 20 | When 'Automatic' is specified the function will use the current computer's AD domain. 21 | 22 | 'Automatic' will be used by default if DnsServer is specified but DnsDomain is not provided. 23 | 24 | .PARAMETER Subnet 25 | 'Automatic' or a comma delimited list of subnets (in CIDR notation) to scan for hosts to inventory. 26 | 27 | When 'Automatic' is specified the function will use the current computer's IP configuration to determine subnets to scan. 28 | 29 | A quick refresher on CIDR notation: 30 | 31 | BITS SUBNET MASK USABLE HOSTS PER SUBNET 32 | ---- --------------- ----------------------- 33 | /20 255.255.240.0 4094 34 | /21 255.255.248.0 2046 35 | /22 255.255.252.0 1022 36 | /23 255.255.254.0 510 37 | /24 255.255.255.0 254 38 | /25 255.255.255.128 126 39 | /26 255.255.255.192 62 40 | /27 255.255.255.224 30 41 | /28 255.255.255.240 14 42 | /29 255.255.255.248 6 43 | /30 255.255.255.252 2 44 | /32 255.255.255.255 1 45 | 46 | .PARAMETER ComputerName 47 | A comma delimited list of computer names to inventory. 48 | 49 | .PARAMETER ExcludeSubnet 50 | A comma delimited list of subnets (in CIDR notation) to exclude when testing for connectivity. 51 | 52 | .PARAMETER LimitSubnet 53 | A comma delimited list of subnets (in CIDR notation) to limit the scope of connectivity tests. Only hosts with IP Addresses that fall within the specified subnet(s) will be included in the results. 54 | 55 | .PARAMETER ExcludeComputerName 56 | A comma delimited list of computer names to exclude when testing for connectivity. Wildcards are accepted. 57 | 58 | An attempt will be made to resolve the IP Address(es) for each computer in this list and those addresses will also be used when determining if a host should be included or excluded when testing for connectivity. 59 | 60 | .PARAMETER MaxConcurrencyThrottle 61 | Number between 1-100 to indicate how many instances to collect information from concurrently. 62 | 63 | If not provided then the number of logical CPUs present to your session will be used. 64 | 65 | .PARAMETER PrivateOnly 66 | Restrict inventory to instances on private class A, B, or C IP addresses 67 | 68 | .PARAMETER AdditionalData 69 | A comma delimited list of additional data to collect as part of the Inventory. 70 | 71 | Valid values include: AdditionalHardware, BIOS, DesktopSessions, EventLog, FullyQualifiedDomainName, InstalledApplications, InstalledPatches, IPRoutes, LastLoggedOnUser, LocalGroups, LocalUserAccounts, None, PowerPlans, Printers, PrintSpoolerLocation, Processes, ProductKeys, RegistrySize, Services, Shares, StartupCommands, WindowsComponents 72 | 73 | Use "None" to bypass collecting all additional information. 74 | 75 | The default value is all listed values excluding "None" 76 | 77 | .PARAMETER DirectoryPath 78 | Specifies the literal path to the directory where the inventory file and log file will be written. 79 | 80 | If not specified then the script defaults to your "My Documents" folder. 81 | 82 | .PARAMETER LoggingPreference 83 | Specifies the logging verbosity to use when writing log entries. 84 | 85 | Valid values include: None, Standard, Verbose, and Debug. 86 | 87 | The default value is "None" 88 | 89 | .PARAMETER Zip 90 | Combine the Inventory and Log files into a single compressed ZIP file. This is useful for transferring the output of an inventory to another machine for further analysis. 91 | 92 | 93 | .EXAMPLE 94 | .\Get-WindowsInventoryToClixml.psm1 -DNSServer automatic -DNSDomain automatic -PrivateOnly 95 | 96 | Description 97 | ----------- 98 | Collect an inventory by querying Active Directory for a list of hosts to scan for Windows machines. The list of hosts will be restricted to private IP addresses only. 99 | 100 | The Inventory file will be written to your "My Documents" folder. 101 | 102 | No Log file will be written. 103 | 104 | .EXAMPLE 105 | .\Get-WindowsInventoryToClixml.psm1 -Subnet 172.20.40.0/28 -LoggingPreference Standard 106 | 107 | Description 108 | ----------- 109 | Collect an inventory by scanning all hosts in the subnet 172.20.40.0/28 for Windows machines. 110 | 111 | The Inventory and Log files will be written to your "My Documents" folder. 112 | 113 | Standard logging will be used. 114 | 115 | .EXAMPLE 116 | .\Get-WindowsInventoryToClixml.psm1 -Computername Server1,Server2,Server3 117 | 118 | Description 119 | ----------- 120 | Collect an inventory by scanning Server1, Server2, and Server3 for Windows machines. 121 | 122 | The Inventory file will be written to your "My Documents" folder. 123 | 124 | No Log file will be written. 125 | 126 | .EXAMPLE 127 | .\Get-WindowsInventoryToClixml.psm1 -Computername $env:COMPUTERNAME -AdditionalData None -LoggingPreference Verbose 128 | 129 | Description 130 | ----------- 131 | Collect an inventory by scanning the local machine for Windows machines. 132 | 133 | Do not collect any data beyond the core set of information. 134 | 135 | The Inventory and Log files will be written to your "My Documents" folder. 136 | 137 | Verbose logging will be used. 138 | 139 | 140 | .OUTPUTS 141 | System.Management.Automation.PSObject 142 | 143 | .NOTES 144 | 145 | .LINK 146 | .\Convert-WindowsInventoryClixmlToExcel.ps1 147 | 148 | #> 149 | [cmdletBinding(SupportsShouldProcess=$false, DefaultParametersetName='dns')] 150 | param( 151 | [Parameter( 152 | Mandatory=$true, 153 | ParameterSetName='dns', 154 | HelpMessage='DNS Server(s)' 155 | )] 156 | [alias('dns')] 157 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^auto$|^automatic$')] 158 | [string[]] 159 | $DnsServer = 'automatic' 160 | , 161 | [Parameter( 162 | Mandatory=$false, 163 | ParameterSetName='dns', 164 | HelpMessage='DNS Domain Name' 165 | )] 166 | [alias("domain")] 167 | [string] 168 | $DnsDomain = 'automatic' 169 | , 170 | [Parameter( 171 | Mandatory=$true, 172 | ParameterSetName='subnet', 173 | HelpMessage='Subnet (in CIDR notation)' 174 | )] 175 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$|^auto$|^automatic$')] 176 | [string[]] 177 | $Subnet = 'automatic' 178 | , 179 | [Parameter( 180 | Mandatory=$true, 181 | ParameterSetName='computername', 182 | HelpMessage='Computer Name(s)' 183 | )] 184 | [alias('computer')] 185 | [string[]] 186 | $ComputerName 187 | , 188 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 189 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 190 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 191 | [string[]] 192 | $ExcludeSubnet 193 | , 194 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 195 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 196 | [string[]] 197 | $LimitSubnet 198 | , 199 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 200 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 201 | [string[]] 202 | $ExcludeComputerName 203 | , 204 | [Parameter(Mandatory=$false)] 205 | [ValidateRange(1,100)] 206 | [byte] 207 | $MaxConcurrencyThrottle = $env:NUMBER_OF_PROCESSORS 208 | , 209 | [Parameter(Mandatory=$false)] 210 | [switch] 211 | $PrivateOnly = $false 212 | , 213 | [Parameter(Mandatory=$false)] 214 | [alias('data')] 215 | [ValidateSet('AdditionalHardware','All','BIOS','DesktopSessions','EventLog','FullyQualifiedDomainName','InstalledApplications','InstalledPatches','IPRoutes', ` 216 | 'LastLoggedOnUser','LocalGroups','LocalUserAccounts','None','PowerPlans','Printers','PrintSpoolerLocation','Processes', ` 217 | 'ProductKeys','RegistrySize','Services','Shares','StartupCommands','WindowsComponents')] 218 | [string[]] 219 | $AdditionalData = @('None') 220 | , 221 | [Parameter(Mandatory=$false)] 222 | [alias('Directory','Path')] 223 | [ValidateNotNullOrEmpty()] 224 | [string] 225 | $DirectoryPath = ([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments)) 226 | , 227 | [Parameter(Mandatory=$false)] 228 | [alias('LogLevel')] 229 | [ValidateSet('none','standard','verbose','debug')] 230 | [string] 231 | $LoggingPreference = 'none' 232 | , 233 | [Parameter(Mandatory=$false)] 234 | [switch] 235 | $Zip = $false 236 | ) 237 | 238 | 239 | ###################### 240 | # FUNCTIONS 241 | ###################### 242 | 243 | function Write-LogMessage { 244 | [CmdletBinding()] 245 | param( 246 | [Parameter(Position=0, Mandatory=$true)] 247 | [ValidateNotNullOrEmpty()] 248 | [System.String] 249 | $Message 250 | , 251 | [Parameter(Position=1, Mandatory=$true)] 252 | [alias('level')] 253 | [ValidateSet('information','verbose','debug','error','warning')] 254 | [System.String] 255 | $MessageLevel 256 | ) 257 | try { 258 | if ((Test-Path -Path 'function:Write-Log') -eq $true) { 259 | Write-Log -Message $Message -MessageLevel $MessageLevel 260 | } else { 261 | Write-Host $Message 262 | } 263 | } 264 | catch { 265 | Throw 266 | } 267 | } 268 | 269 | 270 | function Test-FileIsOpen { 271 | [CmdletBinding()] 272 | [OutputType([System.Boolean])] 273 | param( 274 | [Parameter(Position=0, Mandatory=$true)] 275 | [ValidateNotNullOrEmpty()] 276 | [System.String] 277 | $Path 278 | ) 279 | process { 280 | $FileIsOpen = $false 281 | $Filestream = $null 282 | 283 | try { 284 | $Filestream = [System.IO.File]::Open($ZipFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::None) 285 | $Filestream.Close() 286 | Write-Output $false 287 | } 288 | catch { 289 | Write-Output $true 290 | } 291 | } 292 | } 293 | 294 | 295 | ###################### 296 | # VARIABLES 297 | ###################### 298 | 299 | $Inventory = $null 300 | $ParameterHash = $null 301 | [String]$ZipFilePath = $null 302 | $ZipFile = $null 303 | 304 | $BasePath = (Join-Path -Path $DirectoryPath -ChildPath ('Windows Inventory - ' + (Get-Date -Format 'yyyy-MM-dd-HH-mm'))) 305 | $CliXmlPath = [System.IO.Path]::ChangeExtension($BasePath, 'xml') 306 | $LogPath = [System.IO.Path]::ChangeExtension($BasePath, 'log') 307 | $ZipFilePath = [System.IO.Path]::ChangeExtension($BasePath,'zip') 308 | 309 | # Fallback in case value isn't supplied or somehow missing from the environment variables 310 | if (-not $MaxConcurrencyThrottle) { $MaxConcurrencyThrottle = 1 } 311 | 312 | ###################### 313 | # BEGIN SCRIPT 314 | ###################### 315 | 316 | # Import Modules that we need 317 | Import-Module -Name LogHelper, WindowsInventory 318 | 319 | 320 | # Set logging variables 321 | Set-LogFile -Path $LogPath 322 | Set-LoggingPreference -Preference $LoggingPreference 323 | 324 | Write-LogMessage -Message "Starting Script: $($MyInvocation.MyCommand.Path)" -MessageLevel Information 325 | 326 | 327 | # Build inventory collection command parameters 328 | $ParameterHash = @{ 329 | MaxConcurrencyThrottle = $MaxConcurrencyThrottle 330 | PrivateOnly = $PrivateOnly 331 | AdditionalData = $AdditionalData 332 | } 333 | 334 | switch ($PsCmdlet.ParameterSetName) { 335 | 'dns' { 336 | $ParameterHash.Add('DnsServer',$DnsServer) 337 | $ParameterHash.Add('DnsDomain',$DnsDomain) 338 | if ($ExcludeSubnet) { $ParameterHash.Add('ExcludeSubnet',$ExcludeSubnet) } 339 | if ($LimitSubnet) { $ParameterHash.Add('IncludeSubnet',$LimitSubnet) } 340 | if ($ExcludeComputerName) { $ParameterHash.Add('ExcludeComputerName',$ExcludeComputerName) } 341 | } 342 | 'subnet' { 343 | $ParameterHash.Add('Subnet',$Subnet) 344 | if ($ExcludeSubnet) { $ParameterHash.Add('ExcludeSubnet',$ExcludeSubnet) } 345 | if ($ExcludeComputerName) { $ParameterHash.Add('ExcludeComputerName',$ExcludeComputerName) } 346 | } 347 | 'computername' { 348 | $ParameterHash.Add('ComputerName',$ComputerName) 349 | } 350 | } 351 | 352 | 353 | # Collect inventory and export results to Excel (if there are results in the inventory collection) 354 | $Inventory = Get-WindowsInventory @ParameterHash 355 | 356 | if ($Inventory.ScanSuccessCount -gt 0) { 357 | $Inventory | Export-Clixml -Path $CliXmlPath -Force -Depth 100 -Encoding UTF8 358 | } else { 359 | Write-LogMessage -Message 'No machines found!' -MessageLevel Warning 360 | } 361 | 362 | Write-LogMessage -Message "End Script: $($MyInvocation.MyCommand.Path)" -MessageLevel Information 363 | 364 | 365 | # Try to create compressed file if the option was specified and a log or CliXml file was created 366 | if (($Zip -eq $true) -and ((Test-Path -Path $LogPath) -or (Test-Path -Path $CliXmlPath))) { 367 | 368 | # Create the zip file; if it already exists write a message to the console and skip this part 369 | if ((Test-Path -Path $ZipFilePath) -ne $true) { 370 | 371 | Set-Content -Path $ZipFilePath -Value ("PK" + [char]5 + [char]6 + ("$([char]0)" * 18)) 372 | $ZipFile = (New-Object -ComObject Shell.Application).NameSpace($ZipFilePath) 373 | 374 | # Add log file if it exists 375 | if (($LoggingPreference -ine 'none') -and (Test-Path -Path $LogPath)) { 376 | $ZipFile.CopyHere($LogPath) 377 | 378 | Start-Sleep -Milliseconds 500 379 | while (Test-FileIsOpen -Path $ZipFilePath) { 380 | Start-Sleep -Seconds 1 381 | } 382 | } 383 | 384 | # Add CliXml file if it exists 385 | if (Test-Path -Path $CliXmlPath) { 386 | $ZipFile.CopyHere($CliXmlPath) 387 | 388 | Start-Sleep -Milliseconds 500 389 | while (Test-FileIsOpen -Path $ZipFilePath) { 390 | Start-Sleep -Seconds 1 391 | } 392 | } 393 | 394 | # Remove reference to zip file 395 | $ZipFile = $null 396 | 397 | } else { 398 | Write-LogMessage -Message "Unable to compress files - '$ZipFilePath' already exists" -MessageLevel Error 399 | } 400 | } 401 | 402 | 403 | # Remove Variables 404 | Remove-Variable -Name Inventory, ParameterHash, ZipFilePath, ZipFile, BasePath, CliXmlPath, LogPath 405 | 406 | # Remove Modules 407 | Remove-Module -Name WindowsInventory, LogHelper 408 | 409 | # Call garbage collector 410 | [System.GC]::Collect() 411 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | 3 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | 7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 8 | 9 | A "contribution" is the original software, or any additions or changes to the software. 10 | 11 | A "contributor" is any person that distributes its contribution under this license. 12 | 13 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 14 | 15 | 2. Grant of Rights 16 | 17 | (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 18 | 19 | (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 20 | 21 | 3. Conditions and Limitations 22 | 23 | (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. 24 | 25 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 26 | 27 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 28 | 29 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 30 | 31 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 32 | -------------------------------------------------------------------------------- /Modules/LogHelper/LogHelper.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | # Script module or binary module file associated with this manifest 4 | ModuleToProcess = 'LogHelper' 5 | 6 | # Version number of this module. 7 | ModuleVersion = '1.0.1.0' 8 | 9 | # ID used to uniquely identify this module 10 | GUID = '{9b13d1af-effb-4827-b880-17e106dac3a1}' 11 | 12 | # Author of this module 13 | Author = 'Kendal Van Dyke' 14 | 15 | # Company or vendor of this module 16 | CompanyName = 'Kendal Van Dyke' 17 | 18 | # Copyright statement for this module 19 | Copyright = '(c) 2013. All rights reserved.' 20 | 21 | # Description of the functionality provided by this module 22 | Description = 'Functions to help log script and module information' 23 | 24 | # Minimum version of the Windows PowerShell engine required by this module 25 | PowerShellVersion = '2.0' 26 | 27 | # Minimum version of the .NET Framework required by this module 28 | DotNetFrameworkVersion = '2.0' 29 | 30 | # Minimum version of the common language runtime (CLR) required by this module 31 | CLRVersion = '2.0.50727' 32 | 33 | # Processor architecture (None, X86, Amd64, IA64) required by this module 34 | ProcessorArchitecture = 'None' 35 | 36 | # Modules that must be imported into the global environment prior to importing 37 | # this module 38 | RequiredModules = @() 39 | 40 | # Assemblies that must be loaded prior to importing this module 41 | RequiredAssemblies = @() 42 | 43 | # Script files (.ps1) that are run in the caller's environment prior to 44 | # importing this module 45 | ScriptsToProcess = @() 46 | 47 | # Type files (.ps1xml) to be loaded when importing this module 48 | TypesToProcess = @() 49 | 50 | # Format files (.ps1xml) to be loaded when importing this module 51 | FormatsToProcess = @() 52 | 53 | # Modules to import as nested modules of the module specified in 54 | # ModuleToProcess 55 | NestedModules = @() 56 | 57 | # Functions to export from this module 58 | FunctionsToExport = '*' 59 | 60 | # Cmdlets to export from this module 61 | CmdletsToExport = '*' 62 | 63 | # Variables to export from this module 64 | VariablesToExport = '' 65 | 66 | # Aliases to export from this module 67 | AliasesToExport = '*' 68 | 69 | # List of all modules packaged with this module 70 | ModuleList = @() 71 | 72 | # List of all files packaged with this module 73 | FileList = @('LogHelper.psm1') 74 | 75 | # Private data to pass to the module specified in ModuleToProcess 76 | PrivateData = @{} 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Modules/LogHelper/LogHelper.psm1: -------------------------------------------------------------------------------- 1 | ########################## 2 | # PRIVATE SCRIPT VARIABLES 3 | ########################## 4 | New-Variable -Name LogFile -Value $null -Scope Script -Visibility Private 5 | New-Variable -Name LoggingPreference -Value 'none' -Scope Script -Visibility Private 6 | New-Variable -Name WriteFailureRetry -Value 10 -Scope Script -Visibility Private 7 | New-Variable -Name LogQueue -Value $null -Scope Script -Visibility Private 8 | New-Variable -Name LogQueueIsLocked -Value $false -Scope Script -Visibility Private 9 | 10 | $script:LogQueue = [System.Collections.Queue]::Synchronized((New-Object -TypeName System.Collections.Queue)) 11 | $script:LogFile = [System.IO.Path]::ChangeExtension([IO.Path]::GetTempFileName(),'txt') 12 | 13 | 14 | 15 | ################### 16 | # PUBLIC FUNCTIONS 17 | ################### 18 | function Set-LogFile { 19 | [CmdletBinding()] 20 | param( 21 | [Parameter(Position=0, Mandatory=$true)] 22 | [ValidateNotNullOrEmpty()] 23 | [System.String] 24 | $Path 25 | ) 26 | try { 27 | if ((Split-Path -Path $Path -Parent | Test-Path) -eq $true) { 28 | $script:LogFile = $Path 29 | } else { 30 | throw 'Invalid logfile path specified' 31 | } 32 | } 33 | catch { 34 | throw 35 | } 36 | } 37 | 38 | function Set-LoggingPreference { 39 | [CmdletBinding()] 40 | param( 41 | [Parameter(Position=0, Mandatory=$true)] 42 | #[ValidateSet('none','minimal','normal','verbose')] 43 | [ValidateSet('none','standard','verbose','debug')] 44 | [System.String] 45 | $Preference 46 | ) 47 | try { 48 | $script:LoggingPreference = $Preference 49 | } 50 | catch { 51 | throw 52 | } 53 | } 54 | 55 | function Set-LogQueue { 56 | [CmdletBinding()] 57 | param( 58 | [Parameter(Position=0, Mandatory=$true)] 59 | [ValidateNotNull()] 60 | [System.Collections.Queue] 61 | $Queue 62 | ) 63 | try { 64 | if ($Queue.IsSynchronized) { 65 | $script:LogQueue = $Queue 66 | } else { 67 | $script:LogQueue = [System.Collections.Queue]::Synchronized($Queue) 68 | } 69 | } 70 | catch { 71 | throw 72 | } 73 | } 74 | 75 | function Get-LogFile { 76 | Write-Output $script:LogFile 77 | } 78 | 79 | function Get-LoggingPreference { 80 | Write-Output $script:LoggingPreference 81 | } 82 | 83 | function Write-Log { 84 | <# 85 | .SYNOPSIS 86 | A brief description of the function. 87 | 88 | .DESCRIPTION 89 | A detailed description of the function. 90 | 91 | .PARAMETER ParameterA 92 | The description of the ParameterA parameter. 93 | 94 | .PARAMETER ParameterB 95 | The description of the ParameterB parameter. 96 | 97 | .EXAMPLE 98 | PS C:\> Get-Something -ParameterA 'One value' -ParameterB 32 99 | 100 | .EXAMPLE 101 | PS C:\> Get-Something 'One value' 32 102 | 103 | .INPUTS 104 | System.String,System.Int32 105 | 106 | .OUTPUTS 107 | System.String 108 | 109 | .NOTES 110 | Additional information about the function go here. 111 | 112 | .LINK 113 | about_functions_advanced 114 | 115 | .LINK 116 | about_comment_based_help 117 | 118 | #> 119 | [CmdletBinding()] 120 | param( 121 | [Parameter(Position=0, Mandatory=$true)] 122 | [ValidateNotNullOrEmpty()] 123 | [System.String] 124 | $Message 125 | , 126 | [Parameter(Position=1, Mandatory=$true)] 127 | [alias('level')] 128 | [ValidateSet('information','verbose','debug','error','warning')] 129 | [System.String] 130 | $MessageLevel 131 | ) 132 | try { 133 | $WriteToLog = $false 134 | 135 | # Determine if we're going to write to the log based on the logging preferences and message level 136 | $WriteToLog = switch ($script:LoggingPreference) { 137 | 'debug' { 138 | switch ($MessageLevel) { 139 | 'error' { $true } 140 | 'warning' { $true } 141 | 'information' { $true } 142 | 'verbose' { $true } 143 | 'debug' { $true } 144 | default { $false } 145 | } 146 | } 147 | 'verbose' { 148 | switch ($MessageLevel) { 149 | 'error' { $true } 150 | 'warning' { $true } 151 | 'information' { $true } 152 | 'verbose' { $true } 153 | default { $false } 154 | } 155 | } 156 | 'standard' { 157 | switch ($MessageLevel) { 158 | 'error' { $true } 159 | 'warning' { $true } 160 | 'information' { $true } 161 | default { $false } 162 | } 163 | } 164 | default { 165 | $false 166 | } 167 | } 168 | 169 | if ($WriteToLog -eq $true) { 170 | $Symbol = switch ($MessageLevel) { 171 | 'information' { '?' } 172 | 'verbose' { '$' } 173 | 'debug' { '*' } 174 | 'error' { '!' } 175 | 'warning' { '+' } 176 | } 177 | 178 | $script:LogQueue.Enqueue(("{0} {1} {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.ffff'), $Symbol, $Message)) 179 | 180 | } else { 181 | 182 | # If we're not writing to the log then we need some way to alert on warnings and errors 183 | # so write to the warning and error streams 184 | if ($MessageLevel -ieq 'warning') { 185 | Write-Warning $Message -WarningAction Continue 186 | } 187 | elseif ($MessageLevel -ieq 'error') { 188 | Write-Error $Message -ErrorAction Continue 189 | } 190 | elseif ($MessageLevel -ieq 'verbose') { 191 | Write-Verbose $Message 192 | } 193 | elseif ($MessageLevel -ieq 'debug') { 194 | Write-Debug $Message 195 | } 196 | else { 197 | # For lack of a better "other" stream just write it to the debug stream 198 | Write-Debug $Message 199 | } 200 | 201 | } 202 | 203 | # Putting this outside of the $WriteToLog check b\c items may be in the queue 204 | # although $script:LoggingPreference may have changed (which would change $WriteToLog) 205 | # Another thought: Does this part belong in its own function? Or run as a background job? 206 | try { 207 | # Lock the queue...this prevents anything else from accessing it 208 | [System.Threading.Monitor]::Enter($script:LogQueue.SyncRoot) 209 | $script:LogQueueIsLocked = $true 210 | 211 | # Try and write each message in the queue 212 | # If we fail the item remains in the queue and will be handled on the next log entry 213 | while ($script:LogQueue.Count -gt 0) { 214 | $script:LogQueue.Peek() | Out-File -Encoding Default -FilePath $script:LogFile -Append 215 | $script:LogQueue.Dequeue() | Out-Null 216 | } 217 | } 218 | catch { 219 | } 220 | finally { 221 | if ($script:LogQueueIsLocked -eq $true) { 222 | [System.Threading.Monitor]::Exit($script:LogQueue.SyncRoot) 223 | $script:LogQueueIsLocked = $false 224 | } 225 | } 226 | 227 | } 228 | catch { 229 | Write-Host "$(Get-Date)`tError writing to log file: $Message" 230 | #throw 231 | } 232 | } 233 | 234 | 235 | ############################# 236 | # RUN WHEN MODULE IS UNLOADED 237 | ############################# 238 | $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { 239 | try { 240 | # Lock the queue...this prevents anything else from accessing it 241 | [System.Threading.Monitor]::Enter($script:LogQueue.SyncRoot) 242 | $script:LogQueueIsLocked = $true 243 | 244 | # Try and write each message in the queue 245 | # If we fail the item remains in the queue and will be handled on the next log entry 246 | while ($script:LogQueue.Count -gt 0) { 247 | $script:LogQueue.Peek() | Out-File -Encoding Default -FilePath $script:LogFile -Append 248 | $script:LogQueue.Dequeue() | Out-Null 249 | } 250 | } 251 | catch { 252 | } 253 | finally { 254 | if ($script:LogQueueIsLocked -eq $true) { 255 | [System.Threading.Monitor]::Exit($script:LogQueue.SyncRoot) 256 | $script:LogQueueIsLocked = $false 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /Modules/NetShell/NetShell.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/NetShell/NetShell.psd1 -------------------------------------------------------------------------------- /Modules/NetShell/NetShell.psm1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/NetShell/NetShell.psm1 -------------------------------------------------------------------------------- /Modules/NetworkScan/NetworkScan.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | # Script module or binary module file associated with this manifest 4 | ModuleToProcess = 'NetworkScan' 5 | 6 | # Version number of this module. 7 | ModuleVersion = '1.0.2.1' 8 | 9 | # ID used to uniquely identify this module 10 | GUID = '{d4dba74f-b4b6-46e8-b6aa-1ba66775f3ea}' 11 | 12 | # Author of this module 13 | Author = 'Kendal Van Dyke' 14 | 15 | # Company or vendor of this module 16 | CompanyName = 'Kendal Van Dyke' 17 | 18 | # Copyright statement for this module 19 | Copyright = '(c) 2013. All rights reserved.' 20 | 21 | # Description of the functionality provided by this module 22 | Description = 'Tools to discover hosts on an IPv4 network' 23 | 24 | # Minimum version of the Windows PowerShell engine required by this module 25 | PowerShellVersion = '2.0' 26 | 27 | # Minimum version of the .NET Framework required by this module 28 | DotNetFrameworkVersion = '2.0' 29 | 30 | # Minimum version of the common language runtime (CLR) required by this module 31 | CLRVersion = '2.0.50727' 32 | 33 | # Processor architecture (None, X86, Amd64, IA64) required by this module 34 | ProcessorArchitecture = 'None' 35 | 36 | # Modules that must be imported into the global environment prior to importing 37 | # this module 38 | RequiredModules = @() 39 | 40 | # Assemblies that must be loaded prior to importing this module 41 | RequiredAssemblies = @() 42 | 43 | # Script files (.ps1) that are run in the caller's environment prior to 44 | # importing this module 45 | ScriptsToProcess = @() 46 | 47 | # Type files (.ps1xml) to be loaded when importing this module 48 | TypesToProcess = @() 49 | 50 | # Format files (.ps1xml) to be loaded when importing this module 51 | FormatsToProcess = @() 52 | 53 | # Modules to import as nested modules of the module specified in 54 | # ModuleToProcess 55 | NestedModules = @('NetShell') 56 | 57 | # Functions to export from this module 58 | FunctionsToExport = @('Find-IPv4Device','Find-SqlServerService') 59 | 60 | # Cmdlets to export from this module 61 | #CmdletsToExport = '*' 62 | CmdletsToExport = @() 63 | 64 | # Variables to export from this module 65 | #VariablesToExport = '*' 66 | VariablesToExport = @() 67 | 68 | # Aliases to export from this module 69 | #AliasesToExport = '*' 70 | AliasesToExport = @() 71 | 72 | # List of all modules packaged with this module 73 | ModuleList = @() 74 | 75 | # List of all files packaged with this module 76 | FileList = 'NetworkScan.psm1' 77 | 78 | # Private data to pass to the module specified in ModuleToProcess 79 | PrivateData = @{} 80 | 81 | } -------------------------------------------------------------------------------- /Modules/NetworkScan/NetworkScan.psm1: -------------------------------------------------------------------------------- 1 | ################### 2 | # PRIVATE FUNCTIONS 3 | ################### 4 | function Test-PrivateIPAddress ([System.Net.IPAddress]$IPAddress) { 5 | $PrivateA = "^(10\.){1}(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){2}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" 6 | $PrivateB = "^(172\.){1}(?:(?:1[6-9]|2[0-9]|31?)\.){1}(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.)(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" 7 | $PrivateC = "^(192\.168\.){1}(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){1}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" 8 | 9 | $PrivateIPAddress = $false 10 | 11 | if (($IPAddress -imatch $PrivateA) -or ($IPAddress -imatch $PrivateB) -or ($IPAddress -imatch $PrivateC)) { 12 | $PrivateIPAddress = $true 13 | } else { 14 | $PrivateIPAddress = $true 15 | } 16 | 17 | Write-Output $PrivateIPAddress 18 | } 19 | 20 | function Get-ComputerDomain ([string]$ComputerName) { 21 | 22 | $Win32_ComputerSystem = Get-WmiObject -Namespace root\CIMV2 -Class Win32_ComputerSystem -Property Domain, DomainRole -ComputerName $ComputerName 23 | $ComputerDomain = $null 24 | 25 | if ($Win32_ComputerSystem.DomainRole) { 26 | $ComputerDomain = $Win32_ComputerSystem.Domain 27 | } else { 28 | $ComputerDomain = $null 29 | } 30 | 31 | Write-Output $ComputerDomain 32 | 33 | # The .NET way 34 | # http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory.domain.aspx 35 | # This doesn't work if the host computer is not joined to a domain or unable to contact the AD controller 36 | #Write-Output ([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain()) 37 | } 38 | 39 | function Get-ActiveDirectoryDnsConfiguration { 40 | [CmdletBinding()] 41 | param 42 | ( 43 | [Parameter( 44 | ValueFromPipeline=$True, 45 | ValueFromPipelineByPropertyName=$True, 46 | HelpMessage='What computer name would you like to target?')] 47 | [Alias('host')] 48 | [ValidateLength(3,30)] 49 | [string[]]$ComputerName = "localhost" 50 | ) 51 | process { 52 | 53 | $ActiveDirectoryDnsConfiguration = @() 54 | $ComputerDomain = $null 55 | $Domain = $null 56 | $Computer = $null 57 | 58 | foreach ($Computer in $ComputerName) { 59 | 60 | $ComputerDomain = Get-ComputerDomain -ComputerName $Computer 61 | 62 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_NetworkAdapterConfiguration ` 63 | -Property DnsServerSearchOrder, DNSDomain ` 64 | -Filter "(IPEnabled = True) and (DNSDomain <> 'domain_not_set.invalid') and (DNSDomain <> '')" ` 65 | -ComputerName $Computer | 66 | ForEach-Object { 67 | 68 | # Use the Computer's Domain unless there's a specific domain for this adapter (e.g. VPN connections) 69 | if ($_.DNSDomain) { 70 | $Domain = $_.DNSDomain 71 | } else { 72 | $Domain = $ComputerDomain 73 | } 74 | 75 | if (($Domain) -and ($_.DnsServerSearchOrder)) { 76 | $ActiveDirectoryDnsConfiguration += ( 77 | New-Object -TypeName psobject -Property @{ 78 | ComputerName = $Computer 79 | Domain = $Domain 80 | DnsServer = $_.DnsServerSearchOrder 81 | } 82 | ) 83 | } 84 | } 85 | } 86 | Write-Output $ActiveDirectoryDnsConfiguration 87 | } 88 | } 89 | 90 | function Get-IPv4SubnetConfiguration { 91 | [CmdletBinding()] 92 | param 93 | ( 94 | [Parameter( 95 | ValueFromPipeline=$True, 96 | ValueFromPipelineByPropertyName=$True, 97 | HelpMessage='What computer name would you like to target?')] 98 | [Alias('host')] 99 | [ValidateLength(3,30)] 100 | [string[]]$ComputerName = $env:COMPUTERNAME 101 | ) 102 | process { 103 | $IPv4SubnetConfiguration = @() 104 | $pos = $null 105 | 106 | Get-WmiObject -Namespace root\CIMV2 ` 107 | -Class Win32_NetworkAdapterConfiguration ` 108 | -Property IPAddress, IPSubnet ` 109 | -Filter '(IPEnabled = True)' ` 110 | -ComputerName $ComputerName | 111 | ForEach-Object { 112 | for ($pos = 0; $pos -lt @($_.IPAddress).Count; $pos++) { 113 | 114 | # Only work w\ IPv4 addresses 115 | if (($_.IPAddress[$pos] -as [System.Net.IPAddress]).AddressFamily -ieq 'InterNetwork') { 116 | $IPv4SubnetConfiguration += ( 117 | New-Object -TypeName psobject -Property @{ 118 | IPAddress = $_.IPAddress[$pos] 119 | SubnetMask = $_.IPSubnet[$pos] 120 | } 121 | ) 122 | } 123 | } 124 | } 125 | 126 | Write-Output $IPv4SubnetConfiguration 127 | } 128 | } 129 | 130 | function Get-DnsARecord { 131 | [CmdletBinding()] 132 | param 133 | ( 134 | [Parameter( 135 | Mandatory=$true, 136 | HelpMessage='What is the IP Address of the DNS Server you would like to target?')] 137 | [Alias('ip')] 138 | [System.Net.IPAddress[]]$DnsServerIPAddress 139 | , 140 | [Parameter( 141 | Mandatory=$true, 142 | HelpMessage='What is the Domain you would like to target?')] 143 | [string]$Domain 144 | ) 145 | begin { 146 | $DnsServer = $null 147 | } 148 | process { 149 | Write-Output ( 150 | $DnsServerIPAddress | ForEach-Object { 151 | try { 152 | $DnsServer = $_ 153 | Get-WMIObject -Namespace root\MicrosoftDNS -Class MicrosoftDNS_AType -Computer $_ -Filter "ContainerName= '$Domain'" -ErrorAction Stop | 154 | Where-Object {$_.DomainName -ine $_.OwnerName } | 155 | Select-Object OwnerName, IPAddress | 156 | ForEach-Object { 157 | New-Object -TypeName psobject -Property @{ 158 | OwnerName = $_.OwnerName 159 | IPAddress = $_.IPAddress 160 | } 161 | } 162 | } 163 | catch { 164 | $ThisException = $_.Exception 165 | while ($ThisException.InnerException) { 166 | $ThisException = $ThisException.InnerException 167 | } 168 | Write-NetworkScanLog -Message "Error querying for A records from DNS Server $DnsServer for domain '$Domain': $($ThisException.Message)" -MessageLevel Warning 169 | } 170 | } | Select-Object -Property OwnerName, IPAddress -Unique 171 | ) 172 | } 173 | end { 174 | Remove-Variable -Name DnsServer 175 | } 176 | } 177 | 178 | function Get-DeviceScanObject { 179 | [CmdletBinding()] 180 | [OutputType([System.Int32])] 181 | param( 182 | [Parameter(Mandatory=$false)] 183 | [System.String] 184 | $DnsRecordName = $null 185 | , 186 | [Parameter(Mandatory=$false)] 187 | [System.String] 188 | $WmiMachineName = $null 189 | , 190 | [Parameter(Mandatory=$true)] 191 | [System.Net.IPAddress] 192 | $IPAddress 193 | , 194 | [Parameter(Mandatory=$false)] 195 | [Boolean] 196 | $IsPingAlive = $false 197 | , 198 | [Parameter(Mandatory=$false)] 199 | [Boolean] 200 | $IsWmiAlive = $false 201 | ) 202 | process { 203 | Write-Output ( 204 | New-Object -TypeName PSObject -Property @{ 205 | DnsRecordName = $DnsRecordName 206 | WmiMachineName = $WmiMachineName 207 | IPAddress = $IPAddress 208 | IsPingAlive = $IsPingAlive 209 | IsWmiAlive = $IsWmiAlive 210 | } 211 | ) 212 | } 213 | } 214 | 215 | function Write-NetworkScanLog { 216 | [CmdletBinding()] 217 | param( 218 | [Parameter(Position=0, Mandatory=$true)] 219 | [ValidateNotNullOrEmpty()] 220 | [System.String] 221 | $Message 222 | , 223 | [Parameter(Position=1, Mandatory=$true)] 224 | [alias('level')] 225 | [ValidateSet('information','verbose','debug','error','warning')] 226 | [System.String] 227 | $MessageLevel 228 | ) 229 | try { 230 | if ((Test-Path -Path 'function:Write-Log') -eq $true) { 231 | Write-Log -Message $Message -MessageLevel $MessageLevel 232 | } 233 | } 234 | catch { 235 | Write-Host $Message 236 | #Throw 237 | } 238 | } 239 | 240 | 241 | 242 | ################### 243 | # PUBLIC FUNCTIONS 244 | ################### 245 | 246 | function Find-IPv4Device { 247 | <# 248 | .SYNOPSIS 249 | Synchronously look for IPv4 devices on a network. 250 | 251 | .DESCRIPTION 252 | This function executes a synchronus scan for IPv4 devices on a network. 253 | 254 | When an IPv4 device is found connectivity via Windows Management Interface (WMI) is also verified. 255 | 256 | .PARAMETER DnsServer 257 | 'Automatic', or the Name or IP address of an Active Directory DNS server to query for a list of hosts to test for connectivity 258 | 259 | When 'Automatic' is specified the function will use WMI queries to discover the current computer's DNS server(s) to query. 260 | 261 | .PARAMETER DnsDomain 262 | 'Automatic' or the Active Directory domain name to use when querying DNS for a list of hosts. 263 | 264 | When 'Automatic' is specified the function will use the current computer's AD domain. 265 | 266 | 'Automatic' will be used by default if DnsServer is specified but DnsDomain is not provided. 267 | 268 | .PARAMETER Subnet 269 | 'Automatic' or a comma delimited list of subnets (in CIDR notation) to scan for connectivity. 270 | 271 | When 'Automatic' is specified the function will use the current computer's IP configuration to determine subnets to scan. 272 | 273 | A quick refresher on CIDR notation: 274 | 275 | BITS SUBNET MASK USABLE HOSTS PER SUBNET 276 | ---- --------------- ----------------------- 277 | /20 255.255.240.0 4094 278 | /21 255.255.248.0 2046 279 | /22 255.255.252.0 1022 280 | /23 255.255.254.0 510 281 | /24 255.255.255.0 254 282 | /25 255.255.255.128 126 283 | /26 255.255.255.192 62 284 | /27 255.255.255.224 30 285 | /28 255.255.255.240 14 286 | /29 255.255.255.248 6 287 | /30 255.255.255.252 2 288 | /32 255.255.255.255 1 289 | 290 | .PARAMETER ComputerName 291 | A comma delimited list of computer names to test for connectivity. 292 | 293 | .PARAMETER ExcludeSubnet 294 | A comma delimited list of subnets (in CIDR notation) to exclude when testing for connectivity. 295 | 296 | .PARAMETER LimitSubnet 297 | A comma delimited list of subnets (in CIDR notation) to limit the scope of connectivity tests. Only hosts with IP Addresses that fall within the specified subnet(s) will be included in the results. 298 | 299 | .PARAMETER ExcludeComputerName 300 | A comma delimited list of computer names to exclude when testing for connectivity. Wildcards are accepted. 301 | 302 | An attempt will be made to resolve the IP Address(es) for each computer in this list and those addresses will also be used when determining if a host should be included or excluded when testing for connectivity. 303 | 304 | .PARAMETER MaxConcurrencyThrottle 305 | Number between 1-100 to indicate how many instances to collect information from concurrently. 306 | 307 | If not provided then the number of logical CPUs present to your session will be used. 308 | 309 | .PARAMETER PrivateOnly 310 | Only include hosts with private class A, B, or C IP addresses 311 | 312 | .PARAMETER ResolveAliases 313 | When a mismatch between host name and WMI machine name occurs query DNS for the machine name 314 | 315 | .PARAMETER ParentProgressId 316 | If the caller is using Write-Progress then all progress information will be written using ParentProgressId as the ParentID 317 | 318 | .PARAMETER TimeoutSeconds 319 | Number of seconds to wait for WMI connectivity test to return before timing out. 320 | 321 | If not provided then 30 seconds is used as the default. 322 | 323 | .EXAMPLE 324 | Find-IPv4Device -DNSServer automatic -DNSDomain automatic -PrivateOnly 325 | 326 | Description 327 | ----------- 328 | Queries Active Directory for a list of hosts to scan for IPv4 connectivity. The list of hosts will be restricted to private IP addresses only. 329 | 330 | .EXAMPLE 331 | Find-IPv4Device -Subnet 172.20.40.0/28 332 | 333 | Description 334 | ----------- 335 | Scans all hosts in the subnet 172.20.40.0/28 for IPv4 connectivity. 336 | 337 | .EXAMPLE 338 | Find-IPv4Device -Computername Server1,Server2,Server3 339 | 340 | Description 341 | ----------- 342 | Scanning Server1, Server2, and Server3 for IPv4 connectivity. 343 | 344 | .OUTPUTS 345 | System.Management.Automation.PSObject 346 | 347 | .NOTES 348 | 349 | #> 350 | [CmdletBinding(DefaultParametersetName='dns')] 351 | param( 352 | [Parameter( 353 | Mandatory=$true, 354 | ParameterSetName='dns', 355 | HelpMessage='DNS Server(s)' 356 | )] 357 | [alias('dns')] 358 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^auto$|^automatic$')] 359 | [string[]] 360 | $DnsServer = 'automatic' 361 | , 362 | [Parameter( 363 | Mandatory=$false, 364 | ParameterSetName='dns', 365 | HelpMessage='DNS Domain Name' 366 | )] 367 | [alias('domain')] 368 | [string] 369 | $DnsDomain = 'automatic' 370 | , 371 | [Parameter( 372 | Mandatory=$true, 373 | ParameterSetName='subnet', 374 | HelpMessage='Subnet (in CIDR notation)' 375 | )] 376 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$|^auto$|^automatic$')] 377 | [string[]] 378 | $Subnet = 'Automatic' 379 | , 380 | [Parameter( 381 | Mandatory=$true, 382 | ParameterSetName='computername', 383 | HelpMessage='Computer Name(s)' 384 | )] 385 | [alias('Computer')] 386 | [string[]] 387 | $ComputerName 388 | , 389 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 390 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 391 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 392 | [string[]] 393 | $ExcludeSubnet 394 | , 395 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 396 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 397 | [string[]] 398 | $LimitSubnet 399 | , 400 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 401 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 402 | [string[]] 403 | $ExcludeComputerName 404 | , 405 | [Parameter(Mandatory=$false)] 406 | [ValidateRange(1,100)] 407 | [alias('Throttle')] 408 | [byte] 409 | $MaxConcurrencyThrottle = $env:NUMBER_OF_PROCESSORS 410 | , 411 | [Parameter(Mandatory=$false)] 412 | [switch] 413 | $PrivateOnly = $false 414 | , 415 | [Parameter(Mandatory=$false)] 416 | [switch] 417 | $ResolveAliases = $false 418 | , 419 | [Parameter(Mandatory=$false)] 420 | [ValidateNotNull()] 421 | [Int32] 422 | $ParentProgressId = -1 423 | , 424 | [Parameter(Mandatory=$false)] 425 | [ValidateRange(1,32767)] 426 | [alias('Timeout')] 427 | [Int16] 428 | $TimeoutSeconds = 30 429 | ) 430 | process { 431 | 432 | $Device = @{} 433 | $ActiveDirDnsConfig = $null 434 | $DnsARecord = $null 435 | $PingAliveDevice = $null 436 | $IPv4SubnetConfig = @() 437 | $LimitSubnetNetwork = @() 438 | $ExcludeSubnetNetwork = @() 439 | $ExcludeIpAddress = @() 440 | $IPAddress = $null 441 | $DecimalAddress = $null 442 | $HostName = $null 443 | $ScanCount = 0 444 | $DeviceCount = 0 445 | $HasMetCriteria = $false 446 | 447 | $PingProgressId = Get-Random 448 | $WmiProgressId = Get-Random 449 | 450 | # For use with runspaces 451 | $ScriptBlock = $null 452 | $SessionState = $null 453 | $RunspacePool = $null 454 | $Runspaces = $null 455 | $PowerShell = $null 456 | $HashKey = $null 457 | 458 | # Fallback in case value isn't supplied or somehow missing from the environment variables 459 | if (-not $MaxConcurrencyThrottle) { $MaxConcurrencyThrottle = 1 } 460 | 461 | # Log start status 462 | Write-NetworkScanLog -Message 'Start Function: Find-IPv4Device' -MessageLevel Debug 463 | Write-NetworkScanLog -Message 'Beginning network scan' -MessageLevel Information 464 | 465 | switch ($PsCmdlet.ParameterSetName) { 466 | 'dns' { 467 | Write-NetworkScanLog -Message "`t-DnsServer: $([String]::Join(',',$DnsServer))" -MessageLevel Information 468 | Write-NetworkScanLog -Message "`t-DnsDomain: $DnsDomain" -MessageLevel Information 469 | 470 | } 471 | 'subnet' { 472 | Write-NetworkScanLog -Message "`t-Subnet: $([String]::Join(',',$Subnet))" -MessageLevel Information 473 | 474 | # Initialize variables that don't yet exist in this parameterset 475 | #$LimitSubnet = $false 476 | } 477 | 'computername' { 478 | Write-NetworkScanLog -Message "`t-ComputerName: $([String]::Join(',',$ComputerName))" -MessageLevel Information 479 | 480 | # Initialize variables that don't yet exist in this parameterset 481 | #$ExcludeSubnet = $null 482 | #$LimitSubnet = $null 483 | #$ExcludeComputerName = $null 484 | } 485 | } 486 | if ($ExcludeSubnet) { Write-NetworkScanLog -Message "`t-ExcludeSubnet: $([String]::Join(',',$ExcludeSubnet))" -MessageLevel Information } 487 | if ($LimitSubnet) { Write-NetworkScanLog -Message "`t-LimitSubnet: $([String]::Join(',',$LimitSubnet))" -MessageLevel Information } 488 | if ($ExcludeComputerName) { Write-NetworkScanLog -Message "`t-ExcludeComputerName: $([String]::Join(',',$ExcludeComputerName))" -MessageLevel Information } 489 | Write-NetworkScanLog -Message "`t-PrivateOnly: $PrivateOnly" -MessageLevel Information 490 | Write-NetworkScanLog -Message "`t-MaxConcurrencyThrottle: $MaxConcurrencyThrottle" -MessageLevel Information 491 | Write-NetworkScanLog -Message "`t-ResolveAliases: $ResolveAliases" -MessageLevel Information 492 | 493 | 494 | 495 | # Get network summaries for subnet filtering if exclude\include subnets were provided 496 | if ($LimitSubnet) { 497 | $LimitSubnet | ForEach-Object { 498 | $LimitSubnetNetwork += (Get-NetworkSummary -Network $_) 499 | } 500 | } 501 | if ($ExcludeSubnet) { 502 | $ExcludeSubnet | ForEach-Object { 503 | $ExcludeSubnetNetwork += (Get-NetworkSummary -Network $_) 504 | } 505 | } 506 | 507 | if ($ExcludeComputerName) { 508 | 509 | # Add 'localhost' to the list of excluded computers if $ExcludeComputerName contains the local machine 510 | # Fringe case, I know...but I'm OCD so I put it in here. 511 | if ($ExcludeComputerName -icontains $env:COMPUTERNAME) { 512 | $ExcludeComputerName += 'localhost' 513 | } 514 | 515 | try { 516 | $ExcludeComputerName | 517 | Where-Object { $_.IndexOfAny(@('/','\','[',']','"',':',';','|','<','>','+','=',',','?','*',' ','_')) -eq -1 } | 518 | Select-Object -Unique | 519 | ForEach-Object { 520 | # Get IP Addresses for the host name 521 | # Limit to IPv4 addresses 522 | [System.Net.Dns]::GetHostAddresses($_) | Where-Object { $_.AddressFamily -ieq 'InterNetwork' } | ForEach-Object { 523 | $ExcludeIpAddress += $_.IPAddressToString 524 | } 525 | } 526 | } 527 | catch { 528 | } 529 | } 530 | 531 | 532 | # Get list of IP addresses to scan 533 | #region 534 | switch ($PsCmdlet.ParameterSetName) { 535 | 'dns' { 536 | 537 | # For automatic DNSDomain get this computer's domain setting 538 | if ($DnsDomain -ilike 'auto*') { 539 | $DnsDomain = Get-ComputerDomain -ComputerName localhost 540 | } 541 | 542 | if ($DnsServer -ilike 'auto*') { 543 | $ActiveDirDnsConfig = @(Get-ActiveDirectoryDnsConfiguration -ComputerName localhost) 544 | } else { 545 | $ActiveDirDnsConfig = @( 546 | New-Object -TypeName psobject -Property @{ 547 | ComputerName = $Computer 548 | Domain = $DnsDomain 549 | DnsServer = $DnsServer 550 | } 551 | ) 552 | } 553 | 554 | $ActiveDirDnsConfig | ForEach-Object { 555 | 556 | Write-NetworkScanLog -Message "Querying for A records from DNS Server $($_.DnsServer) for domain '$($_.Domain)'" -MessageLevel Information 557 | 558 | $DnsARecord = Get-DnsARecord -DnsServerIPAddress $_.DnsServer -Domain $_.Domain 559 | 560 | Write-NetworkScanLog -Message "Found $($($DnsARecord | Measure-Object).Count) DNS A records" -MessageLevel Information 561 | 562 | $DnsARecord | Where-Object { $_.IPAddress } | ForEach-Object { 563 | 564 | $HasMetCriteria = $true 565 | 566 | if ( 567 | # Remember PowerShell short circuits -and and -or 568 | $PrivateOnly -ne $true -or 569 | $(Test-PrivateIPAddress -IPAddress $_.IPAddress) -eq $true 570 | ) { 571 | 572 | $HostName = $_.OwnerName.ToUpper() 573 | $IPAddress = $_.IPAddress 574 | 575 | # Check if the host name is in the list of excluded computer names 576 | if ($ExcludeComputerName) { 577 | $ExcludeComputerName | ForEach-Object { 578 | if ($HostName -ilike $_) { 579 | $HasMetCriteria = $false 580 | break 581 | } 582 | } 583 | 584 | # Also check that the IP address isn't in the list of IPs to exclude 585 | if ($ExcludeIpAddress -icontains $IPAddress) { 586 | $HasMetCriteria = $false 587 | } 588 | 589 | } 590 | 591 | # Check if the host IP is within the ranges for $LimitSubnet and $ExcludeSubnet 592 | if ($HasMetCriteria -and 593 | ( $LimitSubnet -or $ExcludeSubnet) 594 | ) { 595 | 596 | $DecimalAddress = ConvertTo-DecimalIP -IPAddress $IPAddress 597 | 598 | if ($LimitSubnet) { 599 | $LimitSubnetNetwork | ForEach-Object { 600 | if (($DecimalAddress -le $_.NetworkDecimal) -or ($DecimalAddress -ge $_.BroadcastDecimal)) { 601 | $HasMetCriteria = $false 602 | } 603 | } 604 | } 605 | 606 | if ($ExcludeSubnet) { 607 | $ExcludeSubnetNetwork | ForEach-Object { 608 | if (($DecimalAddress -ge $_.NetworkDecimal) -and ($DecimalAddress -lt $_.BroadcastDecimal)) { 609 | $HasMetCriteria = $false 610 | } 611 | } 612 | } 613 | 614 | } 615 | 616 | 617 | } 618 | else { 619 | $HasMetCriteria = $false 620 | } 621 | 622 | if ($HasMetCriteria -eq $true) { 623 | if (-not ($Device.Values | Where-Object { ($_.DnsRecordName -ieq $HostName) -and ($_.IPAddress -ieq $IPAddress) })) { 624 | $Device.Add([guid]::NewGuid(), (Get-DeviceScanObject -DnsRecordName $HostName -WmiMachineName $null -IPAddress $IPAddress -IsPingAlive $false -IsWmiAlive $false)) 625 | } 626 | } 627 | } 628 | } 629 | } 630 | 'subnet' { 631 | if ($Subnet -ilike 'auto*') { 632 | $IPv4SubnetConfig = @(Get-IPv4SubnetConfiguration -ComputerName localhost) 633 | } else { 634 | $Subnet | Select-Object -Unique | ForEach-Object { 635 | $IPv4SubnetConfig += ( 636 | Get-NetworkSummary -Network $_ | ForEach-Object { 637 | New-Object -TypeName psobject -Property @{ 638 | IPAddress = $_.NetworkAddress 639 | SubnetMask = $_.Mask 640 | } 641 | } 642 | ) 643 | } 644 | } 645 | 646 | $IPv4SubnetConfig | ForEach-Object { 647 | 648 | Write-NetworkScanLog -Message "Resolving addresses for network $($_.IPAddress) with mask $($_.SubnetMask)" -MessageLevel Information 649 | 650 | if ((($PrivateOnly -eq $true) -and ((Test-PrivateIPAddress -IPAddress $_.IPAddress) -eq $true)) -or ($PrivateOnly -eq $false)) { 651 | $( 652 | if ($_.SubnetMask -ieq '255.255.255.255') { 653 | @( $_.IPAddress ) 654 | } else { 655 | Get-NetworkRange -IPAddress $_.IPAddress -SubnetMask $_.SubnetMask 656 | } 657 | ) | ForEach-Object { 658 | $IPAddress = $_ 659 | $HasMetCriteria = $true 660 | 661 | # Check if the host name is in the list of excluded computer names 662 | if ($ExcludeComputerName) { 663 | # Check that the IP address isn't in the list of IPs to exclude 664 | if ($ExcludeIpAddress -icontains $IPAddress) { 665 | $HasMetCriteria = $false 666 | } 667 | } 668 | 669 | if ($HasMetCriteria -and $ExcludeSubnet) { 670 | $DecimalAddress = ConvertTo-DecimalIP -IPAddress $IPAddress 671 | 672 | $ExcludeSubnetNetwork | ForEach-Object { 673 | if (($DecimalAddress -ge $_.NetworkDecimal) -and ($DecimalAddress -lt $_.BroadcastDecimal)) { 674 | $HasMetCriteria = $false 675 | } 676 | } 677 | } 678 | 679 | # Skip checking - this was causing major performance problems for larger subnets 680 | # and unless overlapping subnets were supplied there's minimal chance of duplicate addresses 681 | #if (-not ($Device.Values | Where-Object { ($_.IPAddress -ieq $IPAddress) })) { 682 | 683 | if ($HasMetCriteria -eq $true) { 684 | $Device.Add([guid]::NewGuid(), (Get-DeviceScanObject -DnsRecordName $null -WmiMachineName $null -IPAddress $IPAddress -IsPingAlive $false -IsWmiAlive $false)) 685 | } 686 | #} 687 | } 688 | } 689 | } 690 | 691 | } 692 | 'computername' { 693 | 694 | $ComputerName | Select-Object -Unique | ForEach-Object { 695 | 696 | $HostName = $_.ToUpper() 697 | Write-NetworkScanLog -Message "Resolving IP address for $HostName" -MessageLevel Information 698 | 699 | try { 700 | $IPAddress = $null 701 | 702 | [System.Net.Dns]::GetHostByName($HostName) | ForEach-Object { 703 | 704 | $HostName = $_.HostName.ToUpper() # Use value from results because it contains the FQDN 705 | 706 | $_.AddressList | Where-Object { $_.AddressFamily -ieq 'InterNetwork' } | ForEach-Object { 707 | if ((($PrivateOnly -eq $true) -and ((Test-PrivateIPAddress -IPAddress $_.IPAddressToString) -eq $true)) -or ($PrivateOnly -eq $false)) { 708 | $Device.Add([guid]::NewGuid(), (Get-DeviceScanObject -DnsRecordName $HostName -WmiMachineName $null -IPAddress $_.IPAddressToString -IsPingAlive $false -IsWmiAlive $false)) 709 | } 710 | } 711 | } 712 | } catch { 713 | Write-NetworkScanLog -Message "Error resolving IP address for $($HostName): $($_.Exception.InnerException.Message)" -MessageLevel Information 714 | } 715 | } 716 | } 717 | } 718 | #endregion 719 | 720 | # Create a Session State, Create a RunspacePool, and open the RunspacePool 721 | $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 722 | $RunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $MaxConcurrencyThrottle, $SessionState, $Host) 723 | $RunspacePool.Open() 724 | 725 | # Create an empty collection to hold the Runspace jobs 726 | $Runspaces = New-Object System.Collections.ArrayList 727 | 728 | $DeviceCount = $Device.Count # Not using Measure-Object we know this starts as an empty hash 729 | $ScanCount = 0 730 | 731 | Write-NetworkScanLog -Message "Testing PING connectivity to $DeviceCount addresses" -MessageLevel Information 732 | Write-Progress -Activity 'Testing PING connectivity' -PercentComplete 0 -Status "Testing $DeviceCount Addresses" -Id $PingProgressId -ParentId $ParentProgressId 733 | 734 | <# PING CONNECTIVITY TEST #> 735 | #region 736 | 737 | $ScanCount = 0 738 | $ScriptBlock = { 739 | Param ( 740 | [String]$ComputerName 741 | ) 742 | Test-Connection -ComputerName $ComputerName -Count 3 -Quiet 743 | } 744 | 745 | 746 | # Queue up PING tests 747 | $Device.GetEnumerator() | ForEach-Object { 748 | 749 | $ScanCount++ 750 | 751 | # Update progress 752 | if ($($_.Value).DnsRecordName) { 753 | Write-NetworkScanLog -Message "Testing PING connectivity to $($($_.Value).DnsRecordName) ($($($_.Value).IPAddress)) [$ScanCount of $DeviceCount]" -MessageLevel Verbose 754 | } elseif ($($_.Value).WmiMachineName) { 755 | Write-NetworkScanLog -Message "Testing PING connectivity to $($($_.Value).WmiMachineName) ($($($_.Value).IPAddress)) [$ScanCount of $DeviceCount]" -MessageLevel Verbose 756 | } else { 757 | Write-NetworkScanLog -Message "Testing PING connectivity to IP address $($($_.Value).IPAddress) [$ScanCount of $DeviceCount]" -MessageLevel Verbose 758 | } 759 | 760 | # Test connectivity to the machine 761 | 762 | #Create the PowerShell instance and supply the scriptblock with the other parameters 763 | $PowerShell = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock) 764 | $PowerShell = $PowerShell.AddArgument($($_.Value).IPAddress) 765 | 766 | #Add the runspace into the PowerShell instance 767 | $PowerShell.RunspacePool = $RunspacePool 768 | 769 | $Runspaces.Add(( 770 | New-Object -TypeName PsObject -Property @{ 771 | PowerShell = $PowerShell 772 | Runspace = $PowerShell.BeginInvoke() 773 | HashKey = $_.Key 774 | } 775 | )) | Out-Null 776 | 777 | } 778 | 779 | # Reset the scan counter 780 | $ScanCount = 0 781 | 782 | # Process results as they complete 783 | Do { 784 | $Runspaces | ForEach-Object { 785 | 786 | If ($_.Runspace.IsCompleted) { 787 | try { 788 | $HashKey = $_.HashKey 789 | 790 | # This is where the output gets returned 791 | $_.PowerShell.EndInvoke($_.Runspace) | ForEach-Object { 792 | $Device[$HashKey].IsPingAlive = $_ 793 | } 794 | } 795 | catch { } 796 | finally { 797 | # Cleanup 798 | $_.PowerShell.dispose() 799 | $_.Runspace = $null 800 | $_.PowerShell = $null 801 | 802 | if ($Device[$HashKey].DnsRecordName) { 803 | Write-NetworkScanLog -Message "PING response from $($Device[$HashKey].DnsRecordName) ($($Device[$HashKey].IPAddress)): $($Device[$HashKey].IsPingAlive)" -MessageLevel Verbose 804 | } elseif ($Device[$HashKey].WmiMachineName) { 805 | Write-NetworkScanLog -Message "PING response from $($Device[$HashKey].WmiMachineName) ($($Device[$HashKey].IPAddress)): $($Device[$HashKey].IsPingAlive)" -MessageLevel Verbose 806 | } else { 807 | Write-NetworkScanLog -Message "PING response from $($Device[$HashKey].IPAddress): $($Device[$HashKey].IsPingAlive)" -MessageLevel Verbose 808 | } 809 | } 810 | } 811 | } 812 | 813 | # Found that in some cases an ACCESS_VIOLATION error occurs if we don't delay a little bit during each iteration 814 | Start-Sleep -Milliseconds 250 815 | 816 | # Clean out unused runspace jobs 817 | $Runspaces.clone() | Where-Object { ($_.Runspace -eq $Null) } | ForEach { 818 | $Runspaces.remove($_) 819 | $ScanCount++ 820 | Write-Progress -Activity 'Testing PING connectivity' -PercentComplete (($ScanCount / $DeviceCount)*100) -Status "$ScanCount of $DeviceCount" -Id $PingProgressId -ParentId $ParentProgressId 821 | } 822 | 823 | } while (($Runspaces | Where-Object {$_.Runspace -ne $Null} | Measure-Object).Count -gt 0) 824 | #endregion 825 | 826 | 827 | # Count how many devices responded 828 | $PingAliveDevice = @($Device.GetEnumerator() | Where-Object { $($_.Value).IsPingAlive -eq $true }) 829 | $DeviceCount = $($PingAliveDevice | Measure-Object).Count 830 | 831 | Write-NetworkScanLog -Message 'PING connectivity test complete' -MessageLevel Verbose 832 | Write-Progress -Activity 'Testing PING connectivity' -PercentComplete 100 -Status "$ScanCount addresses tested, $DeviceCount replies" -Id $PingProgressId -ParentId $ParentProgressId 833 | Write-NetworkScanLog -Message "Testing WMI connectivity to $DeviceCount addresses" -MessageLevel Information 834 | Write-Progress -Activity 'Testing WMI connectivity' -PercentComplete 0 -Status "Testing $DeviceCount Addresses" -Id $WmiProgressId -ParentId $ParentProgressId 835 | 836 | 837 | <# WMI CONNECTIVITY TEST #> 838 | #region 839 | 840 | $ScanCount = 0 841 | $ScriptBlock = { 842 | Param ( 843 | [Net.IPAddress]$IPAddress, 844 | [switch]$IncludeDomainName = $false 845 | ) 846 | 847 | <# 848 | function Get-WMIObjectWithTimeout { 849 | [CmdletBinding()] 850 | param( 851 | [Parameter(Mandatory=$false)] 852 | [Alias('ns')] 853 | [ValidateNotNullOrEmpty()] 854 | [System.String] 855 | $NameSpace = 'root\CIMV2' 856 | , 857 | [Parameter(Mandatory=$true)] 858 | [ValidateNotNull()] 859 | [System.String] 860 | $Class 861 | , 862 | [Parameter(Mandatory=$false)] 863 | [ValidateNotNull()] 864 | [System.String[]] 865 | $Property = @('*') 866 | , 867 | [Parameter(Mandatory=$false)] 868 | [Alias('cn')] 869 | [ValidateNotNull()] 870 | [System.String] 871 | $ComputerName = '.' 872 | , 873 | [Parameter(Mandatory=$false)] 874 | [ValidateNotNull()] 875 | [System.String] 876 | $Filter 877 | , 878 | [Parameter(Mandatory=$false)] 879 | [Alias('timeout')] 880 | [ValidateRange(1,3600)] 881 | [Int] 882 | $TimeoutSeconds = 600 883 | ) 884 | try { 885 | $WmiSearcher = [WMISearcher]'' 886 | $Query = 'select ' + [String]::Join(',',$Property) + ' from ' + $Class 887 | 888 | if ($Filter) { 889 | $Query = "$Query where $Filter" 890 | } 891 | $WmiSearcher.Options.Timeout = [TimeSpan]::FromSeconds($TimeoutSeconds) 892 | $WmiSearcher.Options.ReturnImmediately = $true 893 | $WmiSearcher.Scope.Path = "\\$ComputerName\$NameSpace" 894 | $WmiSearcher.Query = $Query 895 | $WmiSearcher.Get() 896 | } 897 | catch { 898 | Throw 899 | } 900 | } 901 | 902 | $Win32_ComputerSystem = Get-WMIObjectWithTimeout -Namespace root\CIMV2 -Class Win32_ComputerSystem -Property Name -ComputerName $IPAddress -TimeoutSeconds 60 -ErrorAction Stop 903 | #> 904 | 905 | $Win32_ComputerSystem = Get-WMIObject -Namespace root\CIMV2 -Class Win32_ComputerSystem -Property Name -ComputerName $IPAddress -ErrorAction Stop 906 | $ComputerName = $Win32_ComputerSystem.Name 907 | Write-Output $ComputerName 908 | } 909 | 910 | $PingAliveDevice | ForEach-Object { 911 | 912 | $ScanCount++ 913 | 914 | if ($($_.Value).DnsRecordName) { 915 | Write-NetworkScanLog -Message "Testing WMI connectivity to $($($_.Value).DnsRecordName) ($($($_.Value).IPAddress)) [$ScanCount of $DeviceCount]" -MessageLevel Verbose 916 | } elseif ($($_.Value).WmiMachineName) { 917 | Write-NetworkScanLog -Message "Testing WMI connectivity to $($($_.Value).WmiMachineName) ($($($_.Value).IPAddress)) [$ScanCount of $DeviceCount]" -MessageLevel Verbose 918 | } else { 919 | Write-NetworkScanLog -Message "Testing WMI connectivity to IP address $($($_.Value).IPAddress) [$ScanCount of $DeviceCount]" -MessageLevel Verbose 920 | } 921 | 922 | # Create the PowerShell instance and supply the scriptblock with the other parameters 923 | $PowerShell = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock) 924 | $PowerShell = $PowerShell.AddArgument($($_.Value).IPAddress) 925 | 926 | # Add the runspace into the PowerShell instance 927 | $PowerShell.RunspacePool = $RunspacePool 928 | 929 | $Runspaces.Add(( 930 | New-Object -TypeName PsObject -Property @{ 931 | PowerShell = $PowerShell 932 | Runspace = $PowerShell.BeginInvoke() 933 | HashKey = $_.Key 934 | StartDate = [DateTime]::Now 935 | } 936 | )) | Out-Null 937 | 938 | } 939 | 940 | # Reset the scan counter 941 | $ScanCount = 0 942 | 943 | # Process results 944 | Do { 945 | $Runspaces | ForEach-Object { 946 | If ($_.Runspace.IsCompleted) { 947 | try { 948 | 949 | $_.PowerShell.EndInvoke($_.Runspace) | ForEach-Object { 950 | $HostName = $_ 951 | } # This is where the output gets returned 952 | 953 | $HashKey = $_.HashKey 954 | 955 | $Device[$HashKey].WmiMachineName = $HostName # This is where the output gets returned 956 | $Device[$HashKey].IsWmiAlive = $true 957 | 958 | 959 | # Double check that we've got a DNS Hostname. If not, get it from DNS 960 | # If we do have a DNS Hostname that doesn't begin with the WMI Machine Name and $ResolveAliases is true, get the machine name from DNS 961 | if ( 962 | !$Device[$HashKey].DnsRecordName -or 963 | ( 964 | $ResolveAliases -eq $true -and 965 | $Device[$HashKey].DnsRecordName.StartsWith($HostName, 'CurrentCultureIgnoreCase') -ne $true 966 | ) 967 | ) { 968 | try { 969 | [System.Net.Dns]::GetHostByName($HostName) | ForEach-Object { 970 | $Device[$HashKey].DnsRecordName = $_.HostName.ToUpper() 971 | } 972 | } catch { 973 | # Fallback to using the WMI machine name as the DNS host name in the event there's an error 974 | $Device[$HashKey].DnsRecordName = $HostName 975 | } 976 | } 977 | } 978 | catch {} 979 | finally { 980 | # Cleanup 981 | $_.PowerShell.dispose() 982 | $_.Runspace = $null 983 | $_.PowerShell = $null 984 | 985 | if ($Device[$HashKey].DnsRecordName) { 986 | Write-NetworkScanLog -Message "WMI response from $($Device[$HashKey].DnsRecordName) ($($Device[$HashKey].IPAddress)): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Verbose 987 | } elseif ($Device[$HashKey].WmiMachineName) { 988 | Write-NetworkScanLog -Message "WMI response from $($Device[$HashKey].WmiMachineName) ($($Device[$HashKey].IPAddress)): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Verbose 989 | } else { 990 | Write-NetworkScanLog -Message "WMI response from $($Device[$HashKey].IPAddress): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Verbose 991 | } 992 | } 993 | } 994 | elseif ($([DateTime]::Now).Subtract($_.StartDate).TotalSeconds -gt $TimeoutSeconds) { 995 | 996 | $HashKey = $_.HashKey 997 | $_.PowerShell.Stop() 998 | $_.PowerShell.dispose() 999 | $_.Runspace = $null 1000 | $_.PowerShell = $null 1001 | 1002 | if ($Device[$HashKey].DnsRecordName) { 1003 | Write-NetworkScanLog -Message "Timeout waiting for WMI response from $($Device[$HashKey].DnsRecordName) ($($Device[$HashKey].IPAddress))" -MessageLevel Warning 1004 | Write-NetworkScanLog -Message "WMI response from $($Device[$HashKey].DnsRecordName) ($($Device[$HashKey].IPAddress)): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Verbose 1005 | } elseif ($Device[$HashKey].WmiMachineName) { 1006 | Write-NetworkScanLog -Message "Timeout waiting for WMI response from $($Device[$HashKey].WmiMachineName) ($($Device[$HashKey].IPAddress))" -MessageLevel Warning 1007 | Write-NetworkScanLog -Message "WMI response from $($Device[$HashKey].WmiMachineName) ($($Device[$HashKey].IPAddress)): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Verbose 1008 | } else { 1009 | Write-NetworkScanLog -Message "Timeout waiting for WMI response from $($Device[$HashKey].IPAddress): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Warning 1010 | Write-NetworkScanLog -Message "WMI response from $($Device[$HashKey].IPAddress): $($Device[$HashKey].IsWmiAlive)" -MessageLevel Verbose 1011 | } 1012 | } 1013 | } 1014 | 1015 | # Found that in some cases an ACCESS_VIOLATION error occurs if we don't delay a little bit during each iteration 1016 | Start-Sleep -Milliseconds 250 1017 | 1018 | # Clean out unused runspace jobs 1019 | $Runspaces.clone() | Where-Object { ($_.Runspace -eq $Null) } | ForEach { 1020 | $Runspaces.remove($_) 1021 | $ScanCount++ 1022 | Write-Progress -Activity 'Testing WMI connectivity' -PercentComplete (($ScanCount / $DeviceCount)*100) -Status "$ScanCount of $DeviceCount" -Id $WmiProgressId -ParentId $ParentProgressId 1023 | } 1024 | 1025 | } while (($Runspaces | Where-Object {$_.Runspace -ne $Null} | Measure-Object).Count -gt 0) 1026 | 1027 | #endregion 1028 | 1029 | 1030 | # Finally, close the runspaces 1031 | $RunspacePool.close() 1032 | 1033 | Write-NetworkScanLog -Message 'WMI connectivity test complete' -MessageLevel Verbose 1034 | Write-Progress -Activity 'Testing PING connectivity' -PercentComplete 100 -Status 'Complete' -Id $PingProgressId -ParentId $ParentProgressId -Completed 1035 | Write-Progress -Activity 'Testing WMI connectivity' -PercentComplete 100 -Status 'Complete' -Id $WmiProgressId -ParentId $ParentProgressId -Completed 1036 | 1037 | 1038 | # Return results 1039 | Write-Output $Device.Values 1040 | 1041 | Write-NetworkScanLog -Message 'Network scan complete' -MessageLevel Information 1042 | Write-NetworkScanLog -Message "`t-IP Addresses Scanned: $($($Device.Values | Measure-Object).Count)" -MessageLevel Information 1043 | Write-NetworkScanLog -Message "`t-PING Replies: $($($Device.Values | Where-Object { $_.IsPingAlive -eq $true } | Measure-Object).Count)" -MessageLevel Information 1044 | Write-NetworkScanLog -Message "`t-WMI Replies: $($($Device.Values | Where-Object { $_.IsWmiAlive -eq $true } | Measure-Object).Count)" -MessageLevel Information 1045 | 1046 | Write-NetworkScanLog -Message 'End Function: Find-IPv4Device' -MessageLevel Debug 1047 | 1048 | Remove-Variable -Name Device 1049 | 1050 | } 1051 | 1052 | } 1053 | 1054 | function Find-SqlServerService { 1055 | <# 1056 | .SYNOPSIS 1057 | Synchronously look for SQL Server Services on a network. 1058 | 1059 | .DESCRIPTION 1060 | This function executes a synchronus scan for hosts with SQL Server Services on a network. 1061 | 1062 | .PARAMETER DnsServer 1063 | 'Automatic', or the Name or IP address of an Active Directory DNS server to query for a list of hosts to test for connectivity 1064 | 1065 | When 'Automatic' is specified the function will use WMI queries to discover the current computer's DNS server(s) to query. 1066 | 1067 | .PARAMETER DnsDomain 1068 | 'Automatic' or the Active Directory domain name to use when querying DNS for a list of hosts. 1069 | 1070 | When 'Automatic' is specified the function will use the current computer's AD domain. 1071 | 1072 | 'Automatic' will be used by default if DnsServer is specified but DnsDomain is not provided. 1073 | 1074 | .PARAMETER Subnet 1075 | 'Automatic' or a comma delimited list of subnets (in CIDR notation) to scan for connectivity. 1076 | 1077 | When 'Automatic' is specified the function will use the current computer's IP configuration to determine subnets to scan. 1078 | 1079 | A quick refresher on CIDR notation: 1080 | 1081 | BITS SUBNET MASK USABLE HOSTS PER SUBNET 1082 | ---- --------------- ----------------------- 1083 | /20 255.255.240.0 4094 1084 | /21 255.255.248.0 2046 1085 | /22 255.255.252.0 1022 1086 | /23 255.255.254.0 510 1087 | /24 255.255.255.0 254 1088 | /25 255.255.255.128 126 1089 | /26 255.255.255.192 62 1090 | /27 255.255.255.224 30 1091 | /28 255.255.255.240 14 1092 | /29 255.255.255.248 6 1093 | /30 255.255.255.252 2 1094 | /32 255.255.255.255 1 1095 | 1096 | .PARAMETER ComputerName 1097 | A comma delimited list of of computer names to test for SQL Server services. 1098 | 1099 | .PARAMETER ExcludeSubnet 1100 | A comma delimited list of subnets (in CIDR notation) to exclude when testing for connectivity. 1101 | 1102 | .PARAMETER LimitSubnet 1103 | A comma delimited list of subnets (in CIDR notation) to limit the scope of connectivity tests. Only hosts with IP Addresses that fall within the specified subnet(s) will be included in the results. 1104 | 1105 | .PARAMETER ExcludeComputerName 1106 | A comma delimited list of computer names to exclude when testing for connectivity. Wildcards are accepted. 1107 | 1108 | An attempt will be made to resolve the IP Address(es) for each computer in this list and those addresses will also be used when determining if a host should be included or excluded when testing for connectivity. 1109 | 1110 | .PARAMETER MaxConcurrencyThrottle 1111 | Number between 1-100 to indicate how many instances to collect information from concurrently. 1112 | 1113 | If not provided then the number of logical CPUs present to your session will be used. 1114 | 1115 | .PARAMETER PrivateOnly 1116 | Only include hosts with private class A, B, or C IP addresses 1117 | 1118 | .PARAMETER ParentProgressId 1119 | If the caller is using Write-Progress then all progress information will be written using ParentProgressId as the ParentID 1120 | 1121 | .EXAMPLE 1122 | Find-SqlServerService -DNSServer automatic -DNSDomain automatic -PrivateOnly 1123 | 1124 | Description 1125 | ----------- 1126 | Queries Active Directory for a list of hosts to scan for SQL Server services. The list of hosts will be restricted to private IP addresses only. 1127 | 1128 | .EXAMPLE 1129 | Find-SqlServerService -Subnet 172.20.40.0/28 1130 | 1131 | Description 1132 | ----------- 1133 | Scans all hosts in the subnet 172.20.40.0/28 for SQL Server services. 1134 | 1135 | .EXAMPLE 1136 | Find-SqlServerService -Computername Server1,Server2,Server3 1137 | 1138 | Description 1139 | ----------- 1140 | Scanning Server1, Server2, and Server3 for SQL Server services. 1141 | 1142 | .OUTPUTS 1143 | System.Management.Automation.PSObject 1144 | 1145 | .NOTES 1146 | 1147 | #> 1148 | [CmdletBinding(DefaultParametersetName='dns')] 1149 | param( 1150 | [Parameter( 1151 | Mandatory=$true, 1152 | ParameterSetName='dns', 1153 | HelpMessage='DNS Server(s)' 1154 | )] 1155 | [alias('dns')] 1156 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^auto$|^automatic$')] 1157 | [string[]] 1158 | $DnsServer = 'automatic' 1159 | , 1160 | [Parameter( 1161 | Mandatory=$false, 1162 | ParameterSetName='dns', 1163 | HelpMessage='DNS Domain Name' 1164 | )] 1165 | [alias('domain')] 1166 | [string] 1167 | $DnsDomain = 'automatic' 1168 | , 1169 | [Parameter( 1170 | Mandatory=$true, 1171 | ParameterSetName='subnet', 1172 | HelpMessage='Subnet (in CIDR notation)' 1173 | )] 1174 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$|^auto$|^automatic$')] 1175 | [string[]] 1176 | $Subnet = 'Automatic' 1177 | , 1178 | [Parameter( 1179 | Mandatory=$true, 1180 | ParameterSetName='computername', 1181 | HelpMessage='Computer Name(s)' 1182 | )] 1183 | [alias('Computer')] 1184 | [string[]] 1185 | $ComputerName 1186 | , 1187 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 1188 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 1189 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 1190 | [string[]] 1191 | $ExcludeSubnet 1192 | , 1193 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 1194 | [ValidatePattern('^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)[\\/]\d{1,2}$')] 1195 | [string[]] 1196 | $LimitSubnet 1197 | , 1198 | [Parameter(Mandatory=$false, ParameterSetName='dns')] 1199 | [Parameter(Mandatory=$false, ParameterSetName='subnet')] 1200 | [string[]] 1201 | $ExcludeComputerName 1202 | , 1203 | [Parameter(Mandatory=$false)] 1204 | [ValidateRange(1,100)] 1205 | [alias('Throttle')] 1206 | [byte] 1207 | $MaxConcurrencyThrottle = $env:NUMBER_OF_PROCESSORS 1208 | , 1209 | [Parameter(Mandatory=$false)] 1210 | [switch] 1211 | $PrivateOnly = $false 1212 | , 1213 | [Parameter(Mandatory=$false)] 1214 | [ValidateNotNull()] 1215 | [Int32] 1216 | $ParentProgressId = -1 1217 | ) 1218 | process { 1219 | 1220 | $Service = New-Object -TypeName psobject -Property @{ Service = @() } 1221 | $IPv4Device = @() 1222 | $ParameterHash = $null 1223 | $ScanCount = 0 1224 | $WmiDeviceCount = 0 1225 | 1226 | $SqlScanProgressId = Get-Random 1227 | 1228 | # For use with runspaces 1229 | $ScriptBlock = $null 1230 | $SessionState = $null 1231 | $RunspacePool = $null 1232 | $Runspaces = $null 1233 | $Runspace = $null 1234 | $PowerShell = $null 1235 | $HashKey = $null 1236 | 1237 | # Fallback in case value isn't supplied or somehow missing from the environment variables 1238 | if (-not $MaxConcurrencyThrottle) { $MaxConcurrencyThrottle = 1 } 1239 | 1240 | Write-NetworkScanLog -Message 'Start Function: Find-SqlServerService' -MessageLevel Debug 1241 | 1242 | # Build command for splatting 1243 | $ParameterHash = @{ 1244 | MaxConcurrencyThrottle = $MaxConcurrencyThrottle 1245 | PrivateOnly = $PrivateOnly 1246 | ResolveAliases = $true 1247 | ParentProgressId = $ParentProgressId 1248 | } 1249 | 1250 | switch ($PsCmdlet.ParameterSetName) { 1251 | 'dns' { 1252 | $ParameterHash.Add('DnsServer',$DnsServer) 1253 | $ParameterHash.Add('DnsDomain',$DnsDomain) 1254 | if ($ExcludeSubnet) { $ParameterHash.Add('ExcludeSubnet',$ExcludeSubnet) } 1255 | if ($LimitSubnet) { $ParameterHash.Add('IncludeSubnet',$LimitSubnet) } 1256 | if ($ExcludeComputerName) { $ParameterHash.Add('ExcludeComputerName',$ExcludeComputerName) } 1257 | } 1258 | 'subnet' { 1259 | $ParameterHash.Add('Subnet',$Subnet) 1260 | if ($ExcludeSubnet) { $ParameterHash.Add('ExcludeSubnet',$ExcludeSubnet) } 1261 | if ($ExcludeComputerName) { $ParameterHash.Add('ExcludeComputerName',$ExcludeComputerName) } 1262 | } 1263 | 'computername' { 1264 | $ParameterHash.Add('ComputerName',$ComputerName) 1265 | } 1266 | 1267 | } 1268 | 1269 | # Scan the network to find WMI capable (i.e. Windows) devices 1270 | $IPv4Device = (Find-IPv4Device @ParameterHash) 1271 | $WmiDeviceCount = $($IPv4Device | Where-Object { $_.IsWmiAlive -eq $true } | Group-Object -Property DnsRecordName | Measure-Object).Count 1272 | 1273 | Write-NetworkScanLog -Message 'Beginning SQL Service discovery scan' -MessageLevel Information 1274 | Write-Progress -Activity 'Scanning for SQL Services' -PercentComplete 0 -Status "Scanning $WmiDeviceCount Devices" -Id $SqlScanProgressId -ParentId $ParentProgressId 1275 | 1276 | 1277 | # Create a Session State, Create a RunspacePool, and open the RunspacePool 1278 | $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 1279 | $RunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, $MaxConcurrencyThrottle, $SessionState, $Host) 1280 | $RunspacePool.Open() 1281 | 1282 | # Create an empty collection to hold the Runspace jobs 1283 | $Runspaces = New-Object System.Collections.ArrayList 1284 | 1285 | 1286 | $ScanCount = 0 1287 | $ScriptBlock = { 1288 | Param ( 1289 | [String]$IpAddress, 1290 | [String]$ComputerName 1291 | ) 1292 | 1293 | # Load SMO Assemblies 1294 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | ForEach-Object { 1295 | if ($_.GetName().Version.Major -ge 10) { 1296 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMOExtended') | Out-Null 1297 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SQLWMIManagement') | Out-Null 1298 | } 1299 | } 1300 | 1301 | # Registry constants 1302 | New-Variable -Name HKEY_LOCAL_MACHINE -Value 2147483650 -Scope Script -Option Constant 1303 | 1304 | # Variables 1305 | $InstanceName = $null 1306 | $IsNamedInstance = $false 1307 | $IsClusteredInstance = $false 1308 | $ClusterName = $null 1309 | $ServiceIpAddress = $null 1310 | $DomainName = $null 1311 | $ServiceTypeName = $null 1312 | $ManagedComputerServerInstanceName = $null 1313 | $Port = $null 1314 | $IsDynamicPort = $false 1315 | $ServiceStartDate = $null 1316 | $Protocol = $null 1317 | $NetbiosComputerName = if ($ComputerName.IndexOf('.') -gt 15) { 1318 | $ComputerName.Substring(0,15) 1319 | } else { 1320 | $ComputerName.Split('.')[0] 1321 | } 1322 | 1323 | $StdRegProv = $null 1324 | $RegistryKeyRootPath = $null 1325 | $PathName = $null 1326 | $StartMode = $null 1327 | $ProcessId = $null 1328 | $ServiceState = $null 1329 | $ServiceAccount = $null 1330 | $Description = $null 1331 | $RegistryKeyPath = $null 1332 | $Parameters = $null 1333 | $StartupParameters = $null 1334 | 1335 | 1336 | $ManagedComputer = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer' -ArgumentList $ComputerName 1337 | #$ManagedServiceTypeEnum = 'Microsoft.SqlServer.Management.Smo.Wmi.ManagedServiceType' -as [Type] 1338 | 1339 | # Try to use the WMI Managed Computer Object to find SQL Services on $ComputerName 1340 | try { 1341 | $ManagedComputer.Services | ForEach-Object { 1342 | 1343 | if (($_.Name).IndexOf('$') -gt 0) { 1344 | $InstanceName = ($_.Name).Substring(($_.Name).IndexOf('$') + 1) 1345 | $IsNamedInstance = $true 1346 | $ManagedComputerServerInstanceName = $InstanceName 1347 | } else { 1348 | $InstanceName = $null 1349 | $IsNamedInstance = $false 1350 | $ManagedComputerServerInstanceName = $_.Name 1351 | } 1352 | 1353 | # Try and determine if this is a clustered server (and the FQDN & IP for the cluster if it is) 1354 | try { 1355 | if ($_.AdvancedProperties['CLUSTERED'].Value -eq $true) { 1356 | $IsClusteredInstance = $true 1357 | 1358 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_ComputerSystem -Property Domain -ComputerName $IpAddress -ErrorAction Stop | ForEach-Object { 1359 | $DomainName = $_.Domain 1360 | } 1361 | 1362 | $ClusterName = [String]::Join('.', @($_.AdvancedProperties['VSNAME'].Value, $DomainName)).ToUpper() 1363 | 1364 | [System.Net.Dns]::GetHostByName($ClusterName) | ForEach-Object { 1365 | $_.AddressList | Where-Object { $_.AddressFamily -ieq 'InterNetwork' } | ForEach-Object { 1366 | $ServiceIpAddress = $_.IPAddressToString 1367 | } 1368 | } 1369 | 1370 | } else { 1371 | $IsClusteredInstance = $false 1372 | $ClusterName = [String]::Empty 1373 | $ServiceIpAddress = $IpAddress 1374 | } 1375 | } 1376 | catch { 1377 | $IsClusteredInstance = $false 1378 | $ClusterName = [String]::Empty 1379 | $ServiceIpAddress = $IpAddress 1380 | } 1381 | 1382 | 1383 | # Get the Friendly name for the service 1384 | $ServiceTypeName = switch ($_.Type.value__) { 1385 | 5 { 'SQL Server Analysis Services' } 1386 | #$ManagedServiceTypeEnum::NotificationServer { 'SQL Server Notification Services' } 1387 | 6 { 'SQL Server Reporting Services' } 1388 | #$ManagedServiceTypeEnum::Search { 'Microsoft Search service' } 1389 | 3 { 'SQL Server FullText Search' } 1390 | 2 { 'SQL Server Agent' } 1391 | 7 { 'SQL Server Browser' } 1392 | 1 { 'SQL Server' } 1393 | 4 { 'SQL Server Integration Services' } 1394 | 9 { 'SQL Full-text Filter Daemon Launcher' } 1395 | 12 { 'SQL Server Launchpad' } 1396 | $null { 'Unknown' } 1397 | default { $typevalue = $_.Type.value__ 1398 | Write-NetworkScanLog -Message "Typevalue $Typevalue not found. Please define a value for variable 'ServiceTypeName'." -MessageLevel Information} 1399 | } 1400 | 1401 | # Gather protocol details for SQL Server service 1402 | # Not applicable to other service types 1403 | $ServiceIpAddress = $null 1404 | $Port = $null 1405 | $IsDynamicPort = $null 1406 | $ServiceProtocol = $null 1407 | 1408 | if ($ServiceTypeName -ieq 'SQL Server') { 1409 | 1410 | # Gather protocol details 1411 | $ServiceProtocol = $ManagedComputer.ServerInstances | Where-Object { $_.Name -ieq $ManagedComputerServerInstanceName } | ForEach-Object { 1412 | $_.ServerProtocols | Where-Object { -not [String]::IsNullOrEmpty($_.Name) } | ForEach-Object { 1413 | New-Object -TypeName PSObject -Property @{ 1414 | Name = $_.Name 1415 | DisplayName = $_.DisplayName 1416 | IsEnabled = $_.IsEnabled 1417 | IPAddresses = $_.IPAddresses | Where-Object { $_.IPAddress } | ForEach-Object { 1418 | New-Object -TypeName PSObject -Property @{ 1419 | Name = $_.Name 1420 | IpAddress = $_.IPAddress.ToString() 1421 | IpAddressFamily = $_.IPAddress.AddressFamily.ToString() 1422 | IPAddressProperties = $( 1423 | $IpAddressProperty = @{} 1424 | $_.IPAddressProperties | Where-Object { 1425 | -not [String]::IsNullOrEmpty($_.Name) 1426 | } | ForEach-Object { 1427 | $IpAddressProperty += @{ $_.Name = $_.Value } 1428 | } 1429 | Write-Output $IpAddressProperty 1430 | ) 1431 | } 1432 | } 1433 | ProtocolProperties = $( 1434 | $ProtocolProperty = @{} 1435 | $_.ProtocolProperties | Where-Object { 1436 | -not [String]::IsNullOrEmpty($_.Name) -and 1437 | $_.Name -ine 'Enabled' 1438 | } | ForEach-Object { 1439 | $ProtocolProperty += @{ $_.Name = $_.Value } 1440 | } 1441 | Write-Output $ProtocolProperty 1442 | ) 1443 | } 1444 | } 1445 | } 1446 | 1447 | # Determine an IP Address & port the SQL Server service is listening on (if TCP/IP enabled) 1448 | $ServiceProtocol | Where-Object { 1449 | $_.Name -ieq 'tcp' -and 1450 | $_.IsEnabled -eq $true 1451 | } | ForEach-Object { 1452 | 1453 | # If listening on all IPs then get the "IPAll" port info 1454 | # Otherwise get port info for an IP that's enabled and active 1455 | 1456 | if ($_.ProtocolProperties['ListenOnAllIPs'] -eq $true) { 1457 | 1458 | $_.IPAddresses | Where-Object { $_.Name -ieq 'ipall' } | ForEach-Object { 1459 | $Port = $_.IPAddressProperties['TcpPort'] 1460 | if (-not $Port) { 1461 | $Port = $_.IPAddressProperties['TcpDynamicPorts'] 1462 | $IsDynamicPort = $true 1463 | } else { 1464 | $IsDynamicPort = $false 1465 | } 1466 | } 1467 | 1468 | } else { 1469 | 1470 | # Start with 127.0.0.1 first in case that's the only IP that's enabled 1471 | $_.IPAddresses | Where-Object { 1472 | $_.IPAddressProperties['Active'] -eq $true -and 1473 | $_.IPAddressProperties['Enabled'] -eq $true -and 1474 | $_.IPAddressFamily -ieq 'InterNetwork' -and 1475 | $_.IPAddress -ieq '127.0.0.1' 1476 | } | Select-Object -First 1 | ForEach-Object { 1477 | 1478 | $ServiceIpAddress = $_.IPAddress 1479 | $Port = $_.IPAddressProperties['TcpPort'] 1480 | 1481 | if (-not $Port) { 1482 | $Port = $_.IPAddressProperties['TcpDynamicPorts'] 1483 | $IsDynamicPort = $true 1484 | } else { 1485 | $IsDynamicPort = $false 1486 | } 1487 | } 1488 | 1489 | # Now try and see if there's a non-loopback IP enabled 1490 | $_.IPAddresses | Where-Object { 1491 | $_.IPAddressProperties['Active'] -eq $true -and 1492 | $_.IPAddressProperties['Enabled'] -eq $true -and 1493 | $_.IPAddressFamily -ieq 'InterNetwork' -and 1494 | $_.IPAddress -ine '127.0.0.1' 1495 | } | Select-Object -First 1 | ForEach-Object { 1496 | 1497 | $ServiceIpAddress = $_.IPAddress 1498 | $Port = $_.IPAddressProperties['TcpPort'] 1499 | 1500 | if (-not $Port) { 1501 | $Port = $_.IPAddressProperties['TcpDynamicPorts'] 1502 | $IsDynamicPort = $true 1503 | } else { 1504 | $IsDynamicPort = $false 1505 | } 1506 | } 1507 | } 1508 | } 1509 | } 1510 | 1511 | 1512 | # Get the Service Start Date (if it's got a Process ID greater than than 0) 1513 | if ($_.ProcessId -gt 0) { 1514 | try { 1515 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Process -Filter "ProcessId = '$($_.ProcessId)'" -Property CreationDate -ComputerName $IpAddress -ErrorAction Stop | ForEach-Object { 1516 | $ServiceStartDate = $_.ConvertToDateTime($_.CreationDate) 1517 | } 1518 | } 1519 | catch { 1520 | $ServiceStartDate = $null 1521 | } 1522 | } else { 1523 | $ServiceStartDate = $null 1524 | } 1525 | 1526 | Write-Output ( 1527 | New-Object -TypeName psobject -Property @{ 1528 | ComputerName = $ComputerName 1529 | #ClusterName = $ClusterName 1530 | DisplayName = $_.DisplayName 1531 | Description = $_.Description 1532 | ComputerIpAddress = $IpAddress 1533 | #InstanceName = $InstanceName 1534 | IsNamedInstance = $IsNamedInstance 1535 | IsClusteredInstance = $IsClusteredInstance 1536 | IsDynamicPort = $IsDynamicPort 1537 | IsHadrEnabled = $_.IsHadrEnabled 1538 | PathName = $_.PathName 1539 | Port = $Port 1540 | ProcessId = $_.ProcessId 1541 | ServerName = $( 1542 | if ($IsNamedInstance -eq $true) { 1543 | if ($IsClusteredInstance -eq $true) { 1544 | [String]::Join('\', @($ClusterName, $InstanceName)) 1545 | } else { 1546 | if ( 1547 | $ServiceTypeName -ine 'SQL Server' -or 1548 | $( 1549 | $ServiceProtocol | Where-Object { 1550 | $_.Name -ine 'sm' -and 1551 | $_.IsEnabled -eq $true 1552 | } 1553 | ) 1554 | ) { 1555 | # Another protocol besides shared memory is enabled or the service isn't SQL Server\SQL Server Agent; use the FQDN 1556 | [String]::Join('\', @($ComputerName, $InstanceName)) 1557 | } else { 1558 | # Shared memory is the only protocol enabled; use the NETBIOS name 1559 | [String]::Join('\', @($NetbiosComputerName, $InstanceName)) 1560 | } 1561 | } 1562 | } else { 1563 | if ($IsClusteredInstance -eq $true) { 1564 | $ClusterName 1565 | } else { 1566 | if ( 1567 | $ServiceTypeName -ine 'SQL Server' -or 1568 | $( 1569 | $ServiceProtocol | Where-Object { 1570 | $_.Name -ine 'sm' -and 1571 | $_.IsEnabled -eq $true 1572 | } 1573 | ) 1574 | ) { 1575 | # Another protocol besides shared memory is enabled or the service isn't SQL Server\SQL Server Agent; use the FQDN 1576 | $ComputerName 1577 | } else { 1578 | # Shared memory is the only protocol enabled; use the NETBIOS name 1579 | $NetbiosComputerName 1580 | } 1581 | } 1582 | } 1583 | ) 1584 | ServiceIpAddress = $ServiceIpAddress 1585 | ServiceProtocols = $ServiceProtocol 1586 | ServiceStartDate = $ServiceStartDate 1587 | ServiceState = $_.ServiceState.ToString() 1588 | #ServiceType = $_.Type.ToString() 1589 | ServiceTypeName = $ServiceTypeName 1590 | ServiceAccount = $_.ServiceAccount 1591 | StartMode = $_.StartMode.ToString() 1592 | StartupParameters = $_.StartupParameters 1593 | } 1594 | ) 1595 | } 1596 | } 1597 | catch { 1598 | 1599 | # If we get this error it's possible that this is a SQL 2000 server in which case we need to look at the registry to find installed services 1600 | if ($_.Exception.Message -ilike '*SQL Server WMI provider is not available*') { 1601 | try { 1602 | 1603 | # Use the WMI Registry provider to access the registry on $ComputerName 1604 | # For info on using this WMI class see http://msdn.microsoft.com/en-us/library/windows/desktop/aa393664(v=vs.85).aspx 1605 | $StdRegProv = Get-WmiObject -Namespace root\DEFAULT -Query "select * FROM meta_class WHERE __Class = 'StdRegProv'" -ComputerName $ComputerName -ErrorAction Stop 1606 | 1607 | # Iterate through installed instances of the Database Engine (which includes the SQL Agent) 1608 | $($StdRegProv.GetMultiStringValue($HKEY_LOCAL_MACHINE,'SOFTWARE\Microsoft\Microsoft SQL Server','InstalledInstances')).sValue | ForEach-Object { 1609 | 1610 | if ($_ -ine 'MSSQLSERVER') { 1611 | $InstanceName = $_ 1612 | $DisplayName = "MSSQL`$$InstanceName" 1613 | $IsNamedInstance = $true 1614 | $RegistryKeyRootPath = "SOFTWARE\Microsoft\Microsoft SQL Server\$($_)" 1615 | } else { 1616 | $InstanceName = $null 1617 | $DisplayName = 'MSSQLSERVER' 1618 | $IsNamedInstance = $false 1619 | $RegistryKeyRootPath = 'SOFTWARE\Microsoft\MSSQLServer' 1620 | } 1621 | 1622 | # Determine if instance is clustered 1623 | $IsClusteredInstance = $null 1624 | $ClusterName = [String]::Empty 1625 | 1626 | # If $ComputerName is the local host then use the loopback IP, otherwise use $IpAddress 1627 | if ( 1628 | $ComputerName -ieq $env:COMPUTERNAME -or 1629 | $ComputerName.StartsWith([String]::Concat($env:COMPUTERNAME, '.'), [System.StringComparison]::InvariantCultureIgnoreCase) 1630 | ) { 1631 | $ServiceIpAddress = '127.0.0.1' 1632 | } else { 1633 | $ServiceIpAddress = $IpAddress 1634 | } 1635 | 1636 | 1637 | # Get the TCP port number for SQL Server Services 1638 | $Port = ($StdRegProv.GetStringValue($HKEY_LOCAL_MACHINE,"$RegistryKeyRootPath\MSSQLServer\SuperSocketNetLib\Tcp",'TcpDynamicPorts')).sValue 1639 | if (-not $Port) { 1640 | $Port = ($StdRegProv.GetStringValue($HKEY_LOCAL_MACHINE,"$RegistryKeyRootPath\MSSQLServer\SuperSocketNetLib\Tcp",'TcpPort')).sValue 1641 | $IsDynamicPort = $false 1642 | } else { 1643 | $IsDynamicPort = $true 1644 | } 1645 | 1646 | # Get Service Information 1647 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Service ` 1648 | -Filter $('DisplayName = "{0}" OR DisplayName = "SQL Server ({0})"' -f $DisplayName) ` 1649 | -Property PathName,StartMode,ProcessId,State,StartName,Description -ComputerName $IpAddress -ErrorAction Stop | 1650 | ForEach-Object { 1651 | $PathName = $_.PathName 1652 | $StartMode = $_.StartMode 1653 | $ProcessId = $_.ProcessId 1654 | $ServiceState = $_.State 1655 | $ServiceAccount = $_.StartName 1656 | $Description = $_.Description 1657 | } 1658 | 1659 | # Get the Service Start Date (if it's got a Process ID greater than than 0) 1660 | if ($ProcessId -gt 0) { 1661 | try { 1662 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Process -Filter "ProcessId = '$ProcessId'" -Property CreationDate -ComputerName $IpAddress -ErrorAction Stop | ForEach-Object { 1663 | $ServiceStartDate = $_.ConvertToDateTime($_.CreationDate) 1664 | } 1665 | } 1666 | catch { 1667 | $ServiceStartDate = $null 1668 | } 1669 | } else { 1670 | $ServiceStartDate = $null 1671 | } 1672 | 1673 | 1674 | # Startup Parameters 1675 | $RegistryKeyPath = "$RegistryKeyRootPath\MSSQLServer\Parameters" 1676 | $Parameters = $StdRegProv.EnumValues($HKEY_LOCAL_MACHINE,$RegistryKeyPath) 1677 | $StartupParameters = @() 1678 | 1679 | for ($i = 0; $i -lt ($Parameters.sNames | Measure-Object).Count; $i++) { 1680 | switch ($Parameters.Types[$i]) { 1681 | 1 { 1682 | # REG_SZ 1683 | $StartupParameters += $($StdRegProv.GetStringValue($HKEY_LOCAL_MACHINE,$RegistryKeyPath,"$($Parameters.sNames[$i])")).sValue 1684 | } 1685 | 2 { 1686 | # REG_EXPAND_SZ 1687 | $StartupParameters += $($StdRegProv.GetExpandedStringValue($HKEY_LOCAL_MACHINE,$RegistryKeyPath,"$($Parameters.sNames[$i])")).sValue 1688 | } 1689 | 3 { 1690 | # REG_BINARY 1691 | $StartupParameters += [System.BitConverter]::ToString($($StdRegProv.GetBinaryValue($HKEY_LOCAL_MACHINE,$RegistryKeyPath,"$($Parameters.sNames[$i])").uValue) ) 1692 | } 1693 | 4 { 1694 | # REG_DWORD 1695 | $StartupParameters += $($StdRegProv.GetDWORDValue($HKEY_LOCAL_MACHINE,$RegistryKeyPath, "$($Parameters.sNames[$i])")).uValue 1696 | } 1697 | 7 { 1698 | # REG_MULTI_SZ 1699 | $($StdRegProv.GetMultiStringValue($HKEY_LOCAL_MACHINE,$RegistryKeyPath,"$($Parameters.sNames[$i])")).sValue | ForEach-Object { 1700 | $StartupParameters += $_ 1701 | } 1702 | } 1703 | default { $null } 1704 | } 1705 | } 1706 | 1707 | Write-Output ( 1708 | New-Object -TypeName psobject -Property @{ 1709 | ComputerName = $ComputerName 1710 | DisplayName = $DisplayName 1711 | Description = $Description 1712 | ComputerIpAddress = $IpAddress 1713 | IsNamedInstance = $IsNamedInstance 1714 | IsClusteredInstance = $IsClusteredInstance 1715 | IsDynamicPort = $IsDynamicPort 1716 | IsHadrEnabled = $null # Not applicable to SQL 2000 1717 | PathName = $PathName 1718 | Port = $Port 1719 | ProcessId = $ProcessId 1720 | ServerName = $( 1721 | if ($IsNamedInstance -eq $true) { 1722 | if ($IsClusteredInstance -eq $true) { 1723 | [String]::Join('\', @($ClusterName, $InstanceName)) 1724 | } else { 1725 | [String]::Join('\', @($ComputerName, $InstanceName)) 1726 | } 1727 | } else { 1728 | if ($IsClusteredInstance -eq $true) { 1729 | $ClusterName 1730 | } else { 1731 | $ComputerName 1732 | } 1733 | } 1734 | ) 1735 | ServiceIpAddress = $ServiceIpAddress 1736 | ServiceProtocols = $null # TODO: Gather protocol information from registry 1737 | ServiceStartDate = $ServiceStartDate 1738 | ServiceState = $ServiceState 1739 | ServiceTypeName = 'SQL Server' 1740 | ServiceAccount = $ServiceAccount 1741 | StartMode = $StartMode 1742 | StartupParameters = [String]::Join(';', $StartupParameters) 1743 | } 1744 | ) 1745 | 1746 | 1747 | 1748 | # Now let's tackle the SQL Agent. A lot of the information is the same as the SQL Server Service 1749 | if ($IsNamedInstance) { 1750 | $DisplayName = "SQLAgent`$$InstanceName" 1751 | } else { 1752 | $DisplayName = 'SQLSERVERAGENT' 1753 | } 1754 | 1755 | # Get Service Information 1756 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Service ` 1757 | -Filter $('DisplayName = "{0}" OR DisplayName = "SQL Server Agent ({0})"' -f $DisplayName) ` 1758 | -Property PathName,StartMode,ProcessId,State,StartName,Description -ComputerName $IpAddress -ErrorAction Stop | 1759 | ForEach-Object { 1760 | $PathName = $_.PathName 1761 | $StartMode = $_.StartMode 1762 | $ProcessId = $_.ProcessId 1763 | $ServiceState = $_.State 1764 | $ServiceAccount = $_.StartName 1765 | $Description = $_.Description 1766 | } 1767 | 1768 | # Get the Service Start Date (if it's got a Process ID greater than than 0) 1769 | if ($ProcessId -gt 0) { 1770 | try { 1771 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Process -Filter "ProcessId = '$ProcessId'" -Property CreationDate -ComputerName $IpAddress -ErrorAction Stop | ForEach-Object { 1772 | $ServiceStartDate = $_.ConvertToDateTime($_.CreationDate) 1773 | } 1774 | } 1775 | catch { 1776 | $ServiceStartDate = $null 1777 | } 1778 | } else { 1779 | $ServiceStartDate = $null 1780 | } 1781 | 1782 | Write-Output ( 1783 | New-Object -TypeName psobject -Property @{ 1784 | ComputerName = $ComputerName 1785 | DisplayName = $DisplayName 1786 | Description = $Description 1787 | ComputerIpAddress = $IpAddress 1788 | IsNamedInstance = $IsNamedInstance 1789 | IsClusteredInstance = $IsClusteredInstance 1790 | IsDynamicPort = $IsDynamicPort 1791 | IsHadrEnabled = $null # Not applicable to SQL 2000 1792 | PathName = $PathName 1793 | Port = $null 1794 | ProcessId = $ProcessId 1795 | ServerName = $( 1796 | if ($IsNamedInstance -eq $true) { 1797 | if ($IsClusteredInstance -eq $true) { 1798 | [String]::Join('\', @($ClusterName, $InstanceName)) 1799 | } else { 1800 | [String]::Join('\', @($ComputerName, $InstanceName)) 1801 | } 1802 | } else { 1803 | if ($IsClusteredInstance -eq $true) { 1804 | $ClusterName 1805 | } else { 1806 | $ComputerName 1807 | } 1808 | } 1809 | ) 1810 | ServiceIpAddress = $ServiceIpAddress 1811 | ServiceStartDate = $ServiceStartDate 1812 | ServiceState = $ServiceState 1813 | ServiceTypeName = 'SQL Server Agent' 1814 | ServiceAccount = $ServiceAccount 1815 | StartMode = $StartMode 1816 | StartupParameters = $null 1817 | } 1818 | ) 1819 | } 1820 | 1821 | # Now let's test for Analysis Services, Reporting Services, and Microsoft Search. 1822 | # You can't have more than one instance of each in 2000 1823 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Service ` 1824 | -Filter "(DisplayName = 'MSSQLServerOLAPService') or (DisplayName = 'Microsoft Search') or (DisplayName = 'ReportServer')" ` 1825 | -Property DisplayName,PathName,StartMode,ProcessId,State,StartName,Description -ComputerName $IpAddress -ErrorAction Stop | 1826 | ForEach-Object { 1827 | 1828 | $DisplayName = $_.DisplayName 1829 | $PathName = $_.PathName 1830 | $StartMode = $_.StartMode 1831 | $ProcessId = $_.ProcessId 1832 | $ServiceState = $_.State 1833 | $ServiceAccount = $_.StartName 1834 | $Description = $_.Description 1835 | 1836 | # Get the Service Start Date (if it's got a Process ID greater than than 0) 1837 | if ($ProcessId -gt 0) { 1838 | try { 1839 | Get-WmiObject -Namespace root\CIMV2 -Class Win32_Process -Filter "ProcessId = '$ProcessId'" -Property CreationDate -ComputerName $IpAddress -ErrorAction Stop | ForEach-Object { 1840 | $ServiceStartDate = $_.ConvertToDateTime($_.CreationDate) 1841 | } 1842 | } 1843 | catch { 1844 | $ServiceStartDate = $null 1845 | } 1846 | } else { 1847 | $ServiceStartDate = $null 1848 | } 1849 | 1850 | Write-Output ( 1851 | New-Object -TypeName psobject -Property @{ 1852 | ComputerName = $ComputerName 1853 | DisplayName = $DisplayName 1854 | Description = $Description 1855 | ComputerIpAddress = $IpAddress 1856 | IsNamedInstance = $false # Can't have named instances of SSAS, SSRS, or Microsoft Search in SQL 2000 1857 | IsClusteredInstance = $false # Can't cluster SSAS, SSRS, or Microsoft Search in SQL 2000 1858 | IsDynamicPort = $null 1859 | IsHadrEnabled = $null # Not applicable to SQL 2000 1860 | PathName = $PathName 1861 | Port = $null 1862 | ProcessId = $ProcessId 1863 | ServerName = $ComputerName 1864 | ServiceIpAddress = $IpAddress 1865 | ServiceStartDate = $ServiceStartDate 1866 | ServiceState = $ServiceState 1867 | ServiceTypeName = switch ($DisplayName) { 1868 | 'Microsoft Search' { 'Microsoft Search service' } 1869 | 'MSSQLServerOLAPService' { 'SQL Server Analysis Services' } 1870 | 'ReportServer' { 'SQL Server Reporting Services' } 1871 | default { 'Unknown' } 1872 | } 1873 | ServiceAccount = $ServiceAccount 1874 | StartMode = $StartMode 1875 | StartupParameters = $null 1876 | } 1877 | ) 1878 | } 1879 | 1880 | } 1881 | catch { 1882 | throw 1883 | } 1884 | } 1885 | else { 1886 | # Something else has happened; Let the error bubble up 1887 | throw 1888 | } 1889 | } 1890 | } 1891 | 1892 | 1893 | # Iterate through each machine that we could make a WMI connection to and gather information 1894 | # Some machines may have multiple entries (b\c of multiple IP Addresses) so only use the first IP Address for each 1895 | $IPv4Device | Where-Object { $_.IsWmiAlive -eq $true } | Group-Object -Property DnsRecordName | ForEach-Object { 1896 | 1897 | $ScanCount++ 1898 | Write-NetworkScanLog -Message "Scanning $(($_.Group[0]).DnsRecordName) at IP address $(($_.Group[0]).IPAddress) for SQL Services [Device $ScanCount of $WmiDeviceCount]" -MessageLevel Information 1899 | 1900 | #Create the PowerShell instance and supply the scriptblock with the other parameters 1901 | $PowerShell = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock) 1902 | $PowerShell = $PowerShell.AddArgument($($_.Group[0]).IPAddress) 1903 | $PowerShell = $PowerShell.AddArgument($($_.Group[0]).DnsRecordName) 1904 | 1905 | #Add the runspace into the PowerShell instance 1906 | $PowerShell.RunspacePool = $RunspacePool 1907 | 1908 | $Runspaces.Add(( 1909 | New-Object -TypeName PsObject -Property @{ 1910 | PowerShell = $PowerShell 1911 | Runspace = $PowerShell.BeginInvoke() 1912 | ComputerName = $($_.Group[0]).DnsRecordName 1913 | IPAddress = $($_.Group[0]).IPAddress 1914 | } 1915 | )) | Out-Null 1916 | } 1917 | 1918 | # Reset the scan counter 1919 | $ScanCount = 0 1920 | 1921 | # Process results as they complete 1922 | Do { 1923 | foreach ($Runspace in $Runspaces) { 1924 | 1925 | If ($Runspace.Runspace.IsCompleted) { 1926 | try { 1927 | 1928 | # This is where the output gets returned 1929 | $Runspace.PowerShell.EndInvoke($Runspace.Runspace) | ForEach-Object { 1930 | $Service.Service += $_ 1931 | 1932 | if ($_.IsNamedInstance -eq $true) { 1933 | Write-NetworkScanLog -Message "Found $($_.ServiceTypeName) named instance $($_.ServerName) at IP address $($_.ServiceIpAddress)" -MessageLevel Information 1934 | } else { 1935 | Write-NetworkScanLog -Message "Found $($_.ServiceTypeName) default instance $($_.ServerName) at IP address $($_.ServiceIpAddress)" -MessageLevel Information 1936 | } 1937 | } 1938 | 1939 | } 1940 | catch { 1941 | #if ($_.Exception.Message -ilike '*SQL Server WMI provider is not available*') { 1942 | # Write-NetworkScanLog -Message "ERROR: Unable to retrieve service information from $($Runspace.ComputerName) ($($Runspace.IPAddress)). The SQL Server WMI provider may need to be installed on $($Runspace.ComputerName)." -MessageLevel Information 1943 | #} else { 1944 | Write-NetworkScanLog -Message "ERROR: Unable to retrieve service information from $($Runspace.ComputerName) ($($Runspace.IPAddress)): $($_.Exception.Message)" -MessageLevel Information 1945 | #} 1946 | } 1947 | finally { 1948 | # Cleanup 1949 | $Runspace.PowerShell.dispose() 1950 | $Runspace.Runspace = $null 1951 | $Runspace.PowerShell = $null 1952 | } 1953 | } 1954 | } 1955 | 1956 | # Found that in some cases an ACCESS_VIOLATION error occurs if we don't delay a little bit during each iteration 1957 | Start-Sleep -Milliseconds 250 1958 | 1959 | # Clean out unused runspace jobs 1960 | $Runspaces.clone() | Where-Object { ($_.Runspace -eq $Null) } | ForEach { 1961 | $Runspaces.remove($_) 1962 | $ScanCount++ 1963 | Write-Progress -Activity 'Scanning for SQL Services' -PercentComplete (($ScanCount / $WmiDeviceCount)*100) -Status "Device $ScanCount of $WmiDeviceCount" -Id $SqlScanProgressId -ParentId $ParentProgressId 1964 | } 1965 | 1966 | 1967 | } while (($Runspaces | Where-Object {$_.Runspace -ne $Null} | Measure-Object).Count -gt 0) 1968 | #endregion 1969 | 1970 | # Finally, close the runspaces 1971 | $RunspacePool.close() 1972 | 1973 | Write-Progress -Activity 'Scanning for SQL Services' -PercentComplete 100 -Status 'Complete' -Id $SqlScanProgressId -ParentId $ParentProgressId -Completed 1974 | 1975 | Write-NetworkScanLog -Message 'SQL Server service discovery complete' -MessageLevel Information 1976 | 1977 | $Service.Service | Select-Object -Property ServiceTypeName -Unique | Sort-Object -Property ServiceTypeName | ForEach-Object { 1978 | $ServiceTypeName = $_.ServiceTypeName 1979 | Write-NetworkScanLog -Message "`t-$($ServiceTypeName) Instance Count: $(($Service.Service | Where-Object { ($_.ServiceTypeName -ieq $ServiceTypeName) } | Measure-Object).Count)" -MessageLevel Information 1980 | } 1981 | Write-NetworkScanLog -Message 'End Function: Find-SqlServerService' -MessageLevel Debug 1982 | 1983 | # Write output 1984 | Write-Output $Service.Service 1985 | 1986 | } 1987 | 1988 | } -------------------------------------------------------------------------------- /Modules/RDS-Manager/RDS-Manager.psm1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/RDS-Manager/RDS-Manager.psm1 -------------------------------------------------------------------------------- /Modules/SqlServerDatabaseEngineInformation/SqlServerDatabaseEngineInformation.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/SqlServerDatabaseEngineInformation/SqlServerDatabaseEngineInformation.psd1 -------------------------------------------------------------------------------- /Modules/SqlServerInventory/SqlServerInventory.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/SqlServerInventory/SqlServerInventory.psd1 -------------------------------------------------------------------------------- /Modules/WindowsInventory/WindowsInventory.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/WindowsInventory/WindowsInventory.psd1 -------------------------------------------------------------------------------- /Modules/WindowsMachineInformation/WindowsMachineInformation.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | 3 | # Script module or binary module file associated with this manifest 4 | ModuleToProcess = 'WindowsMachineInformation' 5 | 6 | # Version number of this module. 7 | ModuleVersion = '1.0.2.1' 8 | 9 | # ID used to uniquely identify this module 10 | GUID = '{f885012a-cde3-40d4-a7a2-428fb6047839}' 11 | 12 | # Author of this module 13 | Author = 'Kendal Van Dyke' 14 | 15 | # Company or vendor of this module 16 | CompanyName = 'Kendal Van Dyke' 17 | 18 | # Copyright statement for this module 19 | Copyright = '(c) 2013. All rights reserved.' 20 | 21 | # Description of the functionality provided by this module 22 | Description = 'Retrieves information about a Windows Machine using WMI and registry calls' 23 | 24 | # Minimum version of the Windows PowerShell engine required by this module 25 | PowerShellVersion = '2.0' 26 | 27 | # Minimum version of the .NET Framework required by this module 28 | DotNetFrameworkVersion = '2.0' 29 | 30 | # Minimum version of the common language runtime (CLR) required by this module 31 | CLRVersion = '2.0.50727' 32 | 33 | # Processor architecture (None, X86, Amd64, IA64) required by this module 34 | ProcessorArchitecture = 'None' 35 | 36 | # Modules that must be imported into the global environment prior to importing 37 | # this module 38 | RequiredModules = @() 39 | 40 | # Assemblies that must be loaded prior to importing this module 41 | RequiredAssemblies = @() 42 | 43 | # Script files (.ps1) that are run in the caller's environment prior to 44 | # importing this module 45 | ScriptsToProcess = @() 46 | 47 | # Type files (.ps1xml) to be loaded when importing this module 48 | TypesToProcess = @() 49 | 50 | # Format files (.ps1xml) to be loaded when importing this module 51 | FormatsToProcess = @() 52 | 53 | # Modules to import as nested modules of the module specified in 54 | # ModuleToProcess 55 | NestedModules = @() 56 | 57 | # Functions to export from this module 58 | FunctionsToExport = @('Get-WindowsMachineInformation') 59 | 60 | # Cmdlets to export from this module 61 | CmdletsToExport = @() 62 | 63 | # Variables to export from this module 64 | VariablesToExport = '*' 65 | 66 | # Aliases to export from this module 67 | AliasesToExport = @() 68 | 69 | # List of all modules packaged with this module 70 | ModuleList = @() 71 | 72 | # List of all files packaged with this module 73 | FileList = @('WindowsMachineInformation.psm1') 74 | 75 | # Private data to pass to the module specified in ModuleToProcess 76 | PrivateData = @{} 77 | 78 | } -------------------------------------------------------------------------------- /Modules/WindowsMachineInformation/WindowsMachineInformation.psm1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SheepReaper/SQLPowerDoc/d9f60427322ef2321b96a2dfdcd050564b80f63e/Modules/WindowsMachineInformation/WindowsMachineInformation.psm1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **SQL Server & Windows Documentation Using Windows PowerShell** 2 | 3 | SQL Power Doc is a collection of Windows PowerShell scripts and modules that discover, document, and diagnose SQL Server instances and their underlying Windows OS & machine configurations. SQL Power Doc works with all versions of SQL Server from SQL Server 2000 through 2014, and all versions of Windows Server and consumer Windows Operating Systems from Windows 2000 and Windows XP through Windows Server 2012 R2 and Windows 8\. SQL Power Doc is also capable of documenting Windows Azure SQL Databases. 4 | 5 | ## Discover 6 | 7 | Find SQL Server Services on your network by: 8 | 9 | * Active Directory DNS 10 | * Subnet Scan 11 | * Computer Name 12 | 13 | ## Document 14 | 15 | Collect comprehensive details about SQL Server instances and their underlying Windows OS, including: 16 | 17 | * Service Details For All Installed SQL Sever Services 18 | * Database Engine 19 | * Configuration 20 | * Security 21 | * Server Objects 22 | * Databases 23 | * Configuration 24 | * Database Objects 25 | * Service Broker 26 | * Storage 27 | * Security 28 | * SQL Agent 29 | * Configuration 30 | * Jobs 31 | * Alerts 32 | * Operators 33 | * Windows OS 34 | * Machine Information 35 | * OS Information 36 | * Software 37 | 38 | This documentation is useful for: 39 | 40 | * Baselines - know what your SQL Server environment looked like last week, last month, etc. 41 | * Security Audits 42 | * Licensing Audits 43 | * Provide a complete look at how your servers are configured without having to grant access 44 | * Troubleshooting 45 | * Comparing servers and databases 46 | * Creating a runbook that you can give to your operations team 47 | * Planning upgrades - see what hidden features are in use on an instance 48 | 49 | ## Diagnose 50 | 51 | SQL Power Doc performs over 100 checks to find hidden problems and performance bottlenecks on your SQL Servers before they turn into major headaches. 52 | 53 | ## But Wait, There's More! 54 | 55 | SQL Power Doc isn't limited to just SQL Server - you can also use it to collect an inventory of all the Windows machines on your network. If you're in need of a free documentation solution for Windows SQL Power Doc is up to the task! 56 | 57 | ## Ready To Get Started? 58 | 59 | 1. Read the [Requirements](../../wiki/Requirements) and the [How To Guide](../../wiki/Guide-For-PowerShell-Beginners) 60 | 2. (Optional) Read the [ReadMe's](../../tree/master/docs) 61 | 3. [Download](../../releases) the latest version 62 | 4. Collect an inventory 63 | 5. Look like a rockstar 64 | -------------------------------------------------------------------------------- /Tools/SQL Inventory.ps1: -------------------------------------------------------------------------------- 1 | <#================================= 2 | # Generated On: 02/04/2014 3 | # Original By: Microsoft Gallery 4 | # Changed By : Colin Robinson 5 | # Changes : Multiple Minor Updates 6 | #================================= 7 | #> 8 | <#================================= 9 | # last update: 10/07/2018 10 | # Changed By : Lars Platzdasch 11 | # Changes : Minor Updates 12 | #================================= 13 | #> 14 | [CmdletBinding()] 15 | 16 | 17 | $Filename='SQLBasedInventory-' + (Get-Date -Format 'yyyy-MM-dd-HH-mm') 18 | $FilePath = 'C:\Inventory\sqlserverlist.csv' 19 | $DirectoryToSaveTo = 'C:\Inventory\' 20 | 21 | 22 | # before we do anything else, are we likely to be able to save the file? 23 | # if the directory doesn't exist, then create it 24 | if (!(Test-Path -path "$DirectoryToSaveTo")) #create it if not existing 25 | { 26 | New-Item "$DirectoryToSaveTo" -type directory | out-null 27 | } 28 | 29 | #Create a new Excel object using COM 30 | $Excel = New-Object -ComObject Excel.Application 31 | $Excel.visible = $True 32 | $Excel = $Excel.Workbooks.Add() 33 | $Excel.Worksheets.Add() 34 | 35 | $Sheet1 = $Excel.Worksheets.Item(1) 36 | $Sheet2 = $Excel.Worksheets.Item(2) 37 | 38 | #Counter variable for rows 39 | $Sheet1Row = 1 40 | $xlOpenXMLWorkbook=[int]51 41 | 42 | #Read thru the contents of the SQL_Servers.txt file 43 | $Sheet1.Cells.Item($Sheet1Row,1) ="InstanceName" 44 | $Sheet1.Cells.Item($Sheet1Row,2) ="State" 45 | $Sheet1.Cells.Item($Sheet1Row,3) ="Support_Team" 46 | $Sheet1.Cells.Item($Sheet1Row,4) ="ComputerName" 47 | $Sheet1.Cells.Item($Sheet1Row,5) ="NetName" 48 | $Sheet1.Cells.Item($Sheet1Row,6) ="OS" 49 | $Sheet1.Cells.Item($Sheet1Row,7) ="OSVersion" 50 | $Sheet1.Cells.Item($Sheet1Row,8) ="Platform" 51 | $Sheet1.Cells.Item($Sheet1Row,9) ="Product" 52 | $Sheet1.Cells.Item($Sheet1Row,10) ="edition" 53 | $Sheet1.Cells.Item($Sheet1Row,11) ="Version" 54 | $Sheet1.Cells.Item($Sheet1Row,12) ="VersionString" 55 | $Sheet1.Cells.Item($Sheet1Row,13) ="ProductLevel" 56 | $Sheet1.Cells.Item($Sheet1Row,14) ="DatabaseCount" 57 | $Sheet1.Cells.Item($Sheet1Row,15) ="HasNullSaPassword" 58 | $Sheet1.Cells.Item($Sheet1Row,16) ="IsCaseSensitive" 59 | $Sheet1.Cells.Item($Sheet1Row,17) ="IsFullTextInstalled" 60 | $Sheet1.Cells.Item($Sheet1Row,18) ="Language" 61 | $Sheet1.Cells.Item($Sheet1Row,19) ="LoginMode" 62 | $Sheet1.Cells.Item($Sheet1Row,20) ="Processors" 63 | $Sheet1.Cells.Item($Sheet1Row,21) ="PhysicalMemory" 64 | $Sheet1.Cells.Item($Sheet1Row,22) ="MaxMemory" 65 | $Sheet1.Cells.Item($Sheet1Row,23) ="MinMemory" 66 | $Sheet1.Cells.Item($Sheet1Row,24) ="IsSingleUser" 67 | $Sheet1.Cells.Item($Sheet1Row,25) ="IsClustered" 68 | $Sheet1.Cells.Item($Sheet1Row,26) ="Collation" 69 | $Sheet1.Cells.Item($Sheet1Row,27) ="MasterDBLogPath" 70 | $Sheet1.Cells.Item($Sheet1Row,28) ="MasterDBPath" 71 | $Sheet1.Cells.Item($Sheet1Row,29) ="ErrorLogPath" 72 | $Sheet1.Cells.Item($Sheet1Row,30) ="BackupDirectory" 73 | $Sheet1.Cells.Item($Sheet1Row,31) ="DefaultLog" 74 | $Sheet1.Cells.Item($Sheet1Row,32) ="ResourceLastUpdatetime" 75 | $Sheet1.Cells.Item($Sheet1Row,33) ="AuditLevel" 76 | $Sheet1.Cells.Item($Sheet1Row,34) ="DefaultFile" 77 | $Sheet1.Cells.Item($Sheet1Row,35) ="xp_cmdshell" 78 | $Sheet1.Cells.Item($Sheet1Row,36) ="Domain" 79 | $Sheet1.Cells.Item($Sheet1Row,37) ="IPAddress" 80 | 81 | 82 | 83 | $Sheet1.Name = "Sql Servers" 84 | for ($col = 1; $col –le 37; $col++) 85 | { 86 | $Sheet1.Cells.Item($Sheet1Row,$col).Font.Bold = $True 87 | $Sheet1.Cells.Item($Sheet1Row,$col).Interior.ColorIndex = 48 88 | $Sheet1.Cells.Item($Sheet1Row,$col).Font.ColorIndex = 34 89 | } 90 | 91 | $Sheet1Row++ 92 | 93 | #Sheet2 94 | $Sheet2Row = 1 95 | $Sheet2.Name = "DataBases" 96 | $Sheet2.Cells.Item($Sheet2Row,1) ="Support_Team" 97 | $Sheet2.Cells.Item($Sheet2Row,2) ="ComputerName" 98 | $Sheet2.Cells.Item($Sheet2Row,3) ="DataBaseName" 99 | $Sheet2.Cells.Item($Sheet2Row,4) ="DataBaseSize" 100 | $Sheet2.Cells.Item($Sheet2Row,5) ="PrimaryDataFileLocation" 101 | $Sheet2.Cells.Item($Sheet2Row,6) ="LogFileLocation" 102 | 103 | 104 | 105 | 106 | for ($col = 1; $col –le 6; $col++) 107 | { 108 | $Sheet2.Cells.Item($Sheet2Row,$col).Font.Bold = $True 109 | $Sheet2.Cells.Item($Sheet2Row,$col).Interior.ColorIndex = 48 110 | $Sheet2.Cells.Item($Sheet2Row,$col).Font.ColorIndex = 34 111 | } 112 | 113 | 114 | 115 | 116 | $SQLServerList = [object]$sqlServerList = Import-CSV $filepath 117 | 118 | 119 | 120 | foreach ($instanceName in $sqlServerList|WHERE {$_.State -eq 'Active'} ) 121 | { 122 | [System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | out-null 123 | $server1 = New-Object -Type Microsoft.SqlServer.Management.Smo.Server -ArgumentList $instanceName.Server 124 | $s=$server1.Information.Properties |Select Name, Value 125 | $st=$server1.Settings.Properties |Select Name, Value 126 | $CP=$server1.Configuration.Properties |Select DisplayName, Description, RunValue, ConfigValue 127 | $dbs=$server1.Databases.count 128 | $InstanceNameServer = $instanceName.Server 129 | $instanceNameState = $instanceName.State 130 | $instanceNameSupportTeam = $instanceName.SupportTeam 131 | $BuildNumber=$s | where {$_.name -eq "BuildNumber"}|select value 132 | $edition=$s | where {$_.name -eq "edition"}|select value 133 | $ErrorLogPath =$s | where {$_.name -eq "ErrorLogPath"}|select value 134 | $HasNullSaPassword =$s | where {$_.name -eq "HasNullSaPassword"}|select value 135 | $IsCaseSensitive =$s | where {$_.name -eq "IsCaseSensitive"}|select value 136 | $Platform =$s | where {$_.name -eq "Platform"}|select value 137 | $IsFullTextInstalled =$s | where {$_.name -eq "IsFullTextInstalled"}|select value 138 | $Language =$s | where {$_.name -eq "Language"}|select value 139 | $MasterDBLogPath =$s | where {$_.name -eq "MasterDBLogPath"}|select value 140 | $MasterDBPath =$s | where {$_.name -eq "MasterDBPath"}|select value 141 | $NetName =$s | where {$_.name -eq "NetName"}|select value 142 | $OSVersion =$s | where {$_.name -eq "OSVersion"}|select value 143 | $PhysicalMemory =$s | where {$_.name -eq "PhysicalMemory"}|select value 144 | $Processors =$s | where {$_.name -eq "Processors"}|select value 145 | $IsSingleUser =$s | where {$_.name -eq "IsSingleUser"}|select value 146 | $Product =$s | where {$_.name -eq "Product"}|select value 147 | $VersionString =$s | where {$_.name -eq "VersionString"}|select value 148 | $Collation =$s | where {$_.name -eq "Collation"}|select value 149 | $IsClustered =$s | where {$_.name -eq "IsClustered"}|select value 150 | $ProductLevel =$s | where {$_.name -eq "ProductLevel"}|select value 151 | $ComputerNamePhysicalNetBIOS =$s | where {$_.name -eq "ComputerNamePhysicalNetBIOS"}|select value 152 | $ResourceLastUpdateDateTime =$s | where {$_.name -eq "ResourceLastUpdateDateTime"}|select value 153 | $AuditLevel =$st | where {$_.name -eq "AuditLevel"}|select value 154 | $BackupDirectory =$st | where {$_.name -eq "BackupDirectory"}|select value 155 | $DefaultFile =$st | where {$_.name -eq "DefaultFile"}|select value 156 | $DefaultLog =$st | where {$_.name -eq "DefaultLog"}|select value 157 | $LoginMode =$st | where {$_.name -eq "LoginMode"}|select value 158 | $min=$CP | where {$_.Displayname -like "*min server memory*"}|select configValue 159 | $max=$CP | where {$_.Displayname -like "*max server memory*"}|select configValue 160 | $xp_cmdshell=$CP | where {$_.Displayname -like "*xp_cmdshell*"}|select configValue 161 | $FQDN=[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name 162 | $IPAddress=(Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $instanceName.Server)|Where IPAddress 163 | 164 | 165 | 166 | if ($HasNullSaPassword.value -eq $NULL) 167 | { 168 | $HasNullSaPassword.value='No' 169 | } 170 | if($DefaultFile.value -eq '') 171 | { 172 | $DefaultFile.value='NA' 173 | } 174 | if ($VersionString.value -like '8*') 175 | { 176 | $SQLServer='SQL SERVER 2000' 177 | } 178 | elseif ($VersionString.value -like '9*') 179 | { 180 | $SQLServer='SQL SERVER 2005' 181 | } 182 | elseif ($VersionString.value -like '10.0*') 183 | { 184 | $SQLServer='SQL SERVER 2008' 185 | } 186 | elseif ($VersionString.value -like '10.5*') 187 | { 188 | $SQLServer='SQL SERVER 2008 R2' 189 | } 190 | elseif ($VersionString.value -like '11*') 191 | { 192 | $SQLServer='SQL SERVER 2012' 193 | } 194 | elseif ($VersionString.value -like '12*') 195 | { 196 | $SQLServer='SQL SERVER 2014' 197 | } 198 | elseif ($VersionString.value -like '13*') 199 | { 200 | $SQLServer='SQL SERVER 2016' 201 | } 202 | elseif ($VersionString.value -like '14*') 203 | { 204 | $SQLServer='SQL SERVER 2017' 205 | } 206 | else 207 | { 208 | $SQLServer='Invalid' 209 | } 210 | 211 | 212 | if ($OSVersion.value -like '5.0*') 213 | { 214 | $OSVer='Windows 2000' 215 | } 216 | elseif ($OSVersion.value -like '5.1*') 217 | { 218 | $OSVer='Windows XP' 219 | } 220 | elseif ($OSVersion.value -like '5.2*') 221 | { 222 | $OSVer='Windows Server 2003' 223 | } 224 | elseif ($OSVersion.value -like '6.0*') 225 | { 226 | $OSVer='Windows Server 2008' 227 | } 228 | elseif ($OSVersion.value -like '6.1*') 229 | { 230 | $OSVer='Windows Server 2008 R2' 231 | } 232 | elseif ($OSVersion.value -like '6.2*') 233 | { 234 | $OSVer='Windows Server 2012' 235 | } 236 | elseif ($OSVersion.value -like '6.3*') 237 | { 238 | $OSVer='Windows Server 2016' 239 | } 240 | else 241 | { 242 | $OSVer='NA' 243 | } 244 | $Sheet1.Cells.Item($Sheet1Row,1) =$instanceName 245 | $Sheet1.Cells.Item($Sheet1Row,2) =$instanceNameState 246 | $Sheet1.Cells.Item($Sheet1Row,3) =$instanceNameSupportTeam 247 | $Sheet1.Cells.Item($Sheet1Row,4) =$InstanceNameServer 248 | $Sheet1.Cells.Item($Sheet1Row,5) =$NetName.value 249 | $Sheet1.Cells.Item($Sheet1Row,6) =$OSVer 250 | $Sheet1.Cells.Item($Sheet1Row,7) =$OSVersion.value 251 | $Sheet1.Cells.Item($Sheet1Row,8) = $Platform.value 252 | $Sheet1.Cells.Item($Sheet1Row,9) = $Product.value 253 | $Sheet1.Cells.Item($Sheet1Row,10) = $edition.value 254 | $Sheet1.Cells.Item($Sheet1Row,11) = $SQLServer 255 | $Sheet1.Cells.Item($Sheet1Row,12) = $VersionString.value 256 | $Sheet1.Cells.Item($Sheet1Row,13) = $ProductLevel.value 257 | $Sheet1.Cells.Item($Sheet1Row,14) = $Dbs 258 | $Sheet1.Cells.Item($Sheet1Row,15) = $HasNullSaPassword.value 259 | $Sheet1.Cells.Item($Sheet1Row,16) = $IsCaseSensitive.value 260 | $Sheet1.Cells.Item($Sheet1Row,17) = $IsFullTextInstalled.value 261 | $Sheet1.Cells.Item($Sheet1Row,18) = $Language.value 262 | $Sheet1.Cells.Item($Sheet1Row,19) = $LoginMode.value 263 | $Sheet1.Cells.Item($Sheet1Row,20) = $Processors.value 264 | $Sheet1.Cells.Item($Sheet1Row,21) = $PhysicalMemory.value 265 | $Sheet1.Cells.Item($Sheet1Row,22) = $Max.Configvalue 266 | $Sheet1.Cells.Item($Sheet1Row,23) = $Min.Configvalue 267 | $Sheet1.Cells.Item($Sheet1Row,24) = $IsSingleUser.value 268 | $Sheet1.Cells.Item($Sheet1Row,25) = $IsClustered.value 269 | $Sheet1.Cells.Item($Sheet1Row,26) = $Collation.value 270 | $Sheet1.Cells.Item($Sheet1Row,27) = $MasterDBLogPath.value 271 | $Sheet1.Cells.Item($Sheet1Row,28) = $MasterDBPath.value 272 | $Sheet1.Cells.Item($Sheet1Row,29) = $ErrorLogPath.value 273 | $Sheet1.Cells.Item($Sheet1Row,30) = $BackupDirectory.value 274 | $Sheet1.Cells.Item($Sheet1Row,31) = $DefaultLog.value 275 | $Sheet1.Cells.Item($Sheet1Row,32) = $ResourceLastUpdateDateTime.value 276 | $Sheet1.Cells.Item($Sheet1Row,33) = $AuditLevel.value 277 | $Sheet1.Cells.Item($Sheet1Row,34) = $DefaultFile.value 278 | $Sheet1.Cells.Item($Sheet1Row,35) = $xp_cmdshell.Configvalue 279 | $Sheet1.Cells.Item($Sheet1Row,36) = $FQDN 280 | $Sheet1.Cells.Item($Sheet1Row,37) = $IPAddress.IPAddress 281 | $Sheet1Row ++ 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | foreach ($db in $server1.databases) 290 | { 291 | IF ($Sheet2Row -eq 1) #wRITE HEADER ROW 292 | { 293 | $icol = 4 294 | foreach ($Property in $db.properties| where {$_.Name -ne 'ActiveConnections' -and $_.Name -ne 'PolicyHealthState' -and $_.Name -ne 'IsManagementDataWarehouse' -and $_.Name -notlike '*Guid'}) 295 | { 296 | $Sheet2.Cells.Item(1,$icol) = $Property.Name 297 | $Sheet2.Cells.Item(1,$icol).Font.Bold = $True 298 | $Sheet2.Cells.Item(1,$icol).Interior.ColorIndex = 48 299 | $Sheet2.Cells.Item(1,$icol).Font.ColorIndex = 34 300 | $icol++ 301 | } 302 | 303 | } 304 | 305 | If ($db.name -ne 'Master' -and $db.Name -ne 'Model' -and $db.Name -ne 'tempdb' -and $db.Name -ne 'Msdb' ) #Exclude system databases 306 | { $Sheet2Row++ 307 | $Sheet2.Cells.Item($Sheet2Row,1) = $instanceNameSupportTeam #"Support_Team" 308 | $Sheet2.Cells.Item($Sheet2Row,2) = $InstanceNameServer #"ComputerName" 309 | Write-host $InstanceNameServer +','+ $db.Parent 310 | $Sheet2.Cells.Item($Sheet2Row,3) = $db.Name #"DataBaseName" 311 | 312 | $icol = 4 313 | foreach ($Property in $db.properties| where {$_.Name -ne 'ActiveConnections' -and $_.Name -ne 'PolicyHealthState' -and $_.Name -ne 'IsManagementDataWarehouse' -and $_.Name -notlike '*Guid'}) 314 | { 315 | write-host $Property.Name 316 | $Sheet2.Cells.Item($Sheet2Row,$icol) = $Property 317 | $icol++ 318 | } 319 | } 320 | 321 | } 322 | 323 | 324 | 325 | } 326 | 327 | $filename = "$DirectoryToSaveTo$filename.xlsx" 328 | if (test-path $filename ) { rm $filename } #delete the file if it already exists 329 | $Sheet1.UsedRange.EntireColumn.AutoFit() 330 | $Sheet1.Name = "Sql Servers" 331 | cls 332 | $Excel.SaveAs($filename, $xlOpenXMLWorkbook) #save as an XML Workbook (xslx) 333 | $Excel.Saved = $True 334 | $Excel.Close() 335 | 336 | 337 | 338 | 339 | 340 | Function sendEmail([string]$emailFrom, [string]$emailTo, [string]$subject,[string]$body,[string]$smtpServer,[string]$filePath) 341 | { 342 | #initate message 343 | $email = New-Object System.Net.Mail.MailMessage 344 | $email.From = $emailFrom 345 | $email.To.Add($emailTo) 346 | $email.Subject = $subject 347 | $email.Body = $body 348 | # initiate email attachment 349 | $emailAttach = New-Object System.Net.Mail.Attachment $filePath 350 | $email.Attachments.Add($emailAttach) 351 | #initiate sending email 352 | $smtp = new-object Net.Mail.SmtpClient($smtpServer) 353 | $smtp.Send($email) 354 | } 355 | 356 | #Call Function 357 | #sendEmail -emailFrom $from -emailTo $to "SQL INVENTORY" "SQL INVENTORY DETAILS - COMPLETE DETAILS" -smtpServer $SMTP -filePath $filename 358 | -------------------------------------------------------------------------------- /docs/SqlServerInventory Readme.txt: -------------------------------------------------------------------------------- 1 | NOTE: Running a SQL Server Inventory will also perform a Windows Inventory! 2 | 3 | Requirements 4 | ------------- 5 | Before you can document your SQL Server environment with SQL Power Doc you'll to meet the following requirements: 6 | 7 | Permissions 8 | - SQL Power Doc makes connections to standalone SQL Server instances using either Windows Authentication or with a SQL Server username and password. Whichever way you connect, the login will need to be a member of the sysadmin server role on all standalone SQL Server instances you're documenting. 9 | 10 | - For Windows Azure SQL Database (WASD) a SQL username and password is the only way you can connect. This login should be the WASD Administrator login. 11 | 12 | - SQL Power Doc also tries to collect information about the Operating System that SQL Server in installed on. The account used to run SQL Power Doc will need Administrator rights to the OS in order to do this part. 13 | 14 | Windows Machine To Perform Inventory 15 | - This can be virtual or physical - either way it's recommended that its located on the same physical network as the servers you are collecting information from. SQL Power Doc collects a lot of information and you don't want network latency to become a bottleneck 16 | 17 | -- You can ignore this requirement if you're documenting a Windows Azure SQL Database since it's not likely that you'll have physical access to the hardware these databases run on! 18 | 19 | - Just like SQL Server, PowerShell likes memory; For documenting a 10-20 server environment you'll want at least 2 GB of RAM available. Logical CPU count isn't as important, but SQL Power Doc can split its workload across multiple CPUs when doing it's thing so the more CPUs you've got the faster the job will get done. 20 | 21 | - The following software needs to be installed: 22 | -- Windows PowerShell 2.0 or higher: 23 | Windows PowerShell 2.0 is available on on all Windows Operating Systems going back to Windows Server 2003 and Windows XP. Chances are you've already got it installed and enabled, but in case you're not sure head over to http://support.microsoft.com/kb/968929 for a list of requirements and instructions on how to get PowerShell working on your system. 24 | 25 | -- SQL Server Management Objects (SMO): 26 | If SQL Server Management Studio is installed on this machine then it's already got SMO. 27 | 28 | You don't need the absolute latest version installed, but it's a good idea to make sure that the version you do have installed at least matches the highest version of SQL Server that will be included in your inventory 29 | 30 | SMO is part of the SQL 2012 Feature Pack and can be downloaded for free from http://www.microsoft.com/en-us/download/details.aspx?id=29065 (Note - SMO requires the System CLR Types which are on the same download page) 31 | 32 | - Sometimes firewall and group policy restrictions will prevent SQL Power Doc from gathering information from servers. If that's your environment then you'll want to make sure to open up communications for both SQL Server and WMI (Windows Management Instrumentation). Start at http://msdn.microsoft.com/en-us/library/windows/desktop/aa822854(v=vs.85).aspx for instructions on how to do so. 33 | 34 | Windows Machine To Create The Documentation 35 | - Usually this will be your laptop or desktop 36 | 37 | - You'll need the following software installed: 38 | -- Windows PowerShell 2.0 or higher 39 | -- Microsoft Excel 2007 or higher 40 | 41 | - You do not need SMO or access to SQL Server for this step 42 | 43 | You can use the same machine to collect an inventory and create the documentation as long as it meets all the requirements outlined above. This will be the case if you're documenting one or more Windows Azure SQL Databases. 44 | 45 | 46 | 47 | Configure Windows PowerShell 48 | ---------------------------- 49 | You'll want to make sure that PowerShell is configured properly on both the machine that you're using to perform the inventory and the machine that's building the documentation. (Repeat: Do this on both machines!) 50 | 51 | Set Execution Policy 52 | By default PowerShell tries to keep you from shooting yourself in the foot by not letting you run scripts that you download from the internet. In PowerShell lingo this is referred to as the Execution Policy (see http://technet.microsoft.com/en-us/library/hh847748.aspx) and in order to use SQL Power Doc you'll need to change it by following these steps: 53 | 1.Open a PowerShell console in elevated mode: 54 | Start -> All Programs -> Accessories -> Windows PowerShell -> Windows PowerShell (right click, choose "Run as Administrator") 55 | 56 | 2.Set the execution policy to allow for remotely signed scripts 57 | Set-ExecutionPolicy RemoteSigned -Force 58 | 59 | 3.Exit the PowerShell console 60 | 61 | Configure Windows PowerShell Directory 62 | Now you'll need to create a directory to hold PowerShell code. 63 | 1.Open a new PowerShell console (but not in elevated mode as when you set the execution policy): 64 | Start -> All Programs -> Accessories -> Windows PowerShell -> Windows PowerShell 65 | 66 | 2.Create PowerShell and PowerShell modules directory in your "My Documents" folder 67 | New-Item -type directory -path "$([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))\WindowsPowerShell\Modules" 68 | 69 | 3.Exit the PowerShell console 70 | 71 | 72 | Download And Install 73 | -------------------- 74 | Grab the latest version of the code from the downloads page but don't extract the ZIP file yet! Because the file came from the internet it needs to be unblocked or PowerShell gets cranky because it's considered untrusted. 75 | 76 | To unblock a file, navigate to it in Windows Explorer, right click, and choose the Properties menu option. On the General tab, click the Unblock button, then click the OK button to close the Properties dialog. 77 | 78 | Once the file is unblocked you can extract the contents to the WindowsPowerShell folder (in your "My Documents" directory) that you created in the last step. 79 | 80 | Note: Make sure to keep the folder names in the zip file intact so that everything in the Modules folder is extracted into WindowsPowerShell\Modules and the .ps1 files are extracted into the WindowsPowerShell folder. 81 | 82 | The Windows Inventory portion of SQL Power Doc will attempt to use the RDS-Manager PowerShell module provided by the Microsoft Remote Desktop Services team. You can download this optional module from http://gallery.technet.microsoft.com/ScriptCenter/e8c3af96-db10-45b0-88e3-328f087a8700/ . Make sure to save it in the WindowsPowerShell\Modules\RDS-Manager folder (in your "My Documents" directory) that you recently created. It's not the end of the world if it's missing but you'll get more details about users' desktop sessions when it's installed. 83 | 84 | 85 | 86 | Collect A SQL Server And Windows Inventory 87 | ------------------------------------------- 88 | So far, so good...now it's time to discover your SQL Servers and perform an inventory! In this step you're going to run a PowerShell script which will discover SQL Servers on your network (or verify they're running), collect information about them and their underlying OS, and write the results as a Gzip compressed XML file that you'll use in the next step. 89 | 90 | Start by opening a PowerShell console on the machine that will be collecting the information from your SQL Servers and set your current location to the WindowsPowerShell folder: 91 | Set-Location "$([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))\WindowsPowerShell" 92 | 93 | Running The Script, Choosing The Right Parameters 94 | 95 | You're going to execute the script .\Get-SqlServerInventoryToClixml.ps1 to do all the work but it requires a few parameters to know what to do. 96 | 97 | Discover & Verify SQL Server Services 98 | 99 | The first set of parameters define how to find & verify machines with SQL Server services installed on them. 100 | 101 | Find SQL Server services by querying Active Directory DNS for hosts 102 | 103 | -DnsServer 104 | Valid values are "automatic" or a comma delimited list of AD DNS server IP addresses to query for DNS A records that may be running SQL Server. 105 | 106 | -DnsDomain 107 | Optional. Valid values are "automatic" (the default if this parameter is not specified) or the AD domain name to query for DNS records from. 108 | 109 | -ExcludeSubnet 110 | Optional. This is a comma delimited list of CIDR notation subnets to exclude when looking for SQL Servers. 111 | 112 | -LimitSubnet 113 | Optional. This is an inclusive comma delimited list of CIDR notation subnets to limit the scope when looking for SQL Servers. 114 | 115 | -ExcludeComputerName 116 | Optional. This is a comma delimited list of computer names to exclude when looking for SQL Servers. 117 | 118 | -PrivateOnly 119 | Optional. This switch limits the scope to private class A, B, and C IP addresses when looking for SQL Servers. 120 | 121 | 122 | Find SQL Server services by scanning a subnet of IP Addresses 123 | 124 | -Subnet 125 | Valid values are "automatic" or a comma delimited list of CIDR notation subnets to scan for IPv4 hosts that may be running SQL Server. 126 | 127 | -LimitSubnet 128 | Optional. This is an inclusive comma delimited list of CIDR notation subnets to limit the scope when looking for SQL Servers. 129 | 130 | -ExcludeComputerName 131 | Optional. This is a comma delimited list of computer names to exclude when looking for SQL Servers. 132 | 133 | -PrivateOnly 134 | Optional. This switch limits the scope to private class A, B, and C IP addresses when looking for SQL Servers. 135 | 136 | 137 | Find SQL Server services by computer name 138 | 139 | -ComputerName 140 | This is a comma delimited list of computer names that may be running SQL Server. 141 | 142 | -PrivateOnly 143 | Optional. This switch limits the scope to private class A, B, and C IP addresses when looking for SQL Servers. 144 | 145 | 146 | Authentication 147 | 148 | SQL Power Doc will default to using Windows Authentication when attempting to connect to SQL Server instances that it finds. If you want to connect using SQL Server authentication instead, or if you are connecting to Windows Azure SQL Database instances, provide the following parameters: 149 | 150 | -Username 151 | SQL Server username to use when connecting to an instance. 152 | 153 | -Password 154 | SQL Server password to use when connecting to an instance. 155 | 156 | Additional Information To Collect 157 | 158 | By default, SQL Power Doc does not collect Database object information (e.g. tables, views, procedures, etc.), Database object permissions, or information about Database system objects. The following switch parameters alter the default behavior: 159 | 160 | -IncludeDatabaseObjectInformation 161 | Include database object information (e.g. tables, views, procedures, functions, etc.). 162 | 163 | -IncludeDatabaseObjectPermissions 164 | Include database object permissions (e.g. GRANT SELECT on tables, columns, views, etc.). 165 | 166 | -IncludeDatabaseSystemObjects 167 | Include system objects when collecting information about database objects and\or database object permissions. 168 | 169 | Logging, Output, & Resource Utilization 170 | 171 | Finally, the following parameters control logging, output, and system resources SQL Power Doc will use when finding and collecting information from SQL Servers: 172 | 173 | -DirectoryPath 174 | Optional. A fully qualified directory path where all output will be written. The default value is your "My Documents" folder. 175 | 176 | -LoggingPreference 177 | Optional. Specifies how much information will be written to a log file (in the same directory as the output file). Valid values are None, Standard, Verbose, and Debug. The default value is None (i.e. no logging). 178 | 179 | -Zip 180 | Optional. If provided, create a Zip file containing all output in the directory specified by the DirectoryPath parameter. 181 | 182 | -MaxConcurrencyThrottle 183 | Optional. A number between 1-100 which indicates how many tasks to perform concurrently. The default is the number of logical CPUs present on the OS. 184 | 185 | 186 | Examples 187 | 188 | The following examples demonstrate how to combine all the parameters together when running the script. 189 | 190 | Example 1: 191 | 192 | .\Get-SqlServerInventoryToClixml.ps1 -DNSServer automatic -DNSDomain automatic -PrivateOnly 193 | 194 | Collect an inventory by querying Active Directory for a list of hosts to scan for SQL Server instances. 195 | 196 | The list of hosts will be restricted to private IP addresses only. 197 | 198 | Windows Authentication will be used to connect to each instance. 199 | 200 | Database objects will NOT be included in the results. 201 | 202 | The Inventory file will be written to your "My Documents" folder. 203 | 204 | No log file will be written. 205 | 206 | 207 | 208 | Example 2: 209 | 210 | .\Get-SqlServerInventoryToClixml.ps1 -Subnet 172.20.40.0/28 -Username sa -Password BetterNotBeBlank 211 | 212 | Collect an inventory by scanning all hosts in the subnet 172.20.40.0/28 for SQL Server instances. 213 | 214 | SQL authentication (username = "sa", password = "BetterNotBeBlank") will be used to connect to the instance. 215 | 216 | Database objects will NOT be included in the results. 217 | 218 | The Inventory file will be written to your "My Documents" folder. 219 | 220 | No log file will be written. 221 | 222 | 223 | Example 3: 224 | 225 | .\Get-SqlServerInventoryToClixml.ps1 -Computername Server1,Server2,Server3 -LoggingPreference Standard 226 | 227 | Collect an inventory by scanning Server1, Server2, and Server3 for SQL Server instances. 228 | 229 | Windows Authentication will be used to connect to the instance. 230 | 231 | Database objects will NOT be included in the results. 232 | 233 | The Inventory file will be written to your "My Documents" folder. 234 | 235 | Standard logging will be used. 236 | 237 | 238 | Example 4: 239 | 240 | .\Get-SqlServerInventoryToClixml.ps1 -Computername $env:COMPUTERNAME -IncludeDatabaseObjectInformation -LoggingPreference Verbose 241 | 242 | Collect an inventory by scanning the local machine for SQL Server instances. 243 | 244 | Windows Authentication will be used to connect to the instance. 245 | 246 | Database objects (EXCLUDING system objects) will be included in the results. 247 | 248 | The Inventory file will be written to your "My Documents" folder. 249 | 250 | Verbose logging will be used. 251 | 252 | 253 | Example 5: 254 | 255 | .\Get-SqlServerInventoryToClixml.ps1 -Computername $env:COMPUTERNAME -IncludeDatabaseObjectInformation -IncludeDatabaseSystemObjects 256 | 257 | Collect an inventory by scanning the local machine for SQL Server instances. 258 | 259 | Windows Authentication will be used to connect to the instance. 260 | 261 | Database objects (INCLUDING system objects) will be included in the results. 262 | 263 | 264 | 265 | How Long Will It Take? 266 | ---------------------- 267 | When run with the defaults (i.e. do not collect database object information or permissions) on a machine with 2 CPUs you can expect the script to take about 15-20 minutes to complete an inventory of 20 instances. Your mileage will vary depending on how many instances you are collecting information from and what additional information you're including. 268 | 269 | A few suggestions\observations: 270 | 271 | - Features have been added to each version of SQL Server so it stands to reason that the more recent your versions of SQL Server are, the longer it will take to collect information from them - especially if you include database object information and system objects as part of the inventory. 272 | 273 | - Progress is written to the PowerShell console to give you a better idea what the script's up to. If you've got logging enabled (highly recommended) you can also check the logs for progress updates. 274 | 275 | - Just like any other software it's possible to run into issues if you try and do too much. In other words, you may have more success getting the script to complete in a reasonable amount of time if you limit what it's doing. If you're collecting database object information and permissions, consider limiting the number of instances you include in the inventory. Do you really need to include ALL of development, QA, test, and production in a single inventory? 276 | 277 | - SQL Server has a LOT of system objects under the covers so unless you have a good reason to, don't bother collecting system object details...and if you need to, you may want to limit the inventory to a few machines at a time. 278 | 279 | 280 | 281 | Generate A SQL Inventory Report 282 | ------------------------------- 283 | Once the inventory collection phase is complete you'll want to copy the output file to the machine where you'll create the inventory reports (Excel workbooks). 284 | 285 | To create an inventory report, start by opening a PowerShell console and set your current location to the WindowsPowerShell folder: 286 | Set-Location "$([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))\WindowsPowerShell" 287 | 288 | This time you're going to execute the script .\Convert-SqlServerInventoryClixmlToExcel.ps1 and supply the following parameters: 289 | 290 | -FromPath 291 | 292 | The literal path to the output file created by Get-SqlServerInventoryToClixml.ps1. 293 | 294 | -ToDirectoryPath 295 | Optional. Specifies the literal path to the directory where the Excel workbooks will be written. This path must exist prior to executing the script. If this parameter is not provided the workbooks will be written to the same directory specified in the FromPath parameter. 296 | 297 | 298 | -ColorTheme 299 | Optional. An Office Theme Color to apply to each worksheet. If not specified or if an unknown theme color is provided the default "Office" theme colors will be used. 300 | 301 | Office 2013 theme colors include: 302 | Aspect, Blue Green, Blue II, Blue Warm, Blue, Grayscale, Green Yellow, Green, Marquee, Median, Office, Office 2007 - 2010, Orange Red, Orange, Paper, Red Orange, Red Violet, Red, Slipstream, Violet II, Violet, Yellow Orange, Yellow 303 | 304 | Office 2010 theme colors include: 305 | Adjacency, Angles, Apex, Apothecary, Aspect, Austin, Black Tie, Civic, Clarity, Composite, Concourse, Couture, Elemental, Equity, Essential, Executive, Flow, Foundry, Grayscale, Grid, Hardcover, Horizon, Median, Metro, Module, Newsprint, Office, Opulent, Oriel, Origin, Paper, Perspective, Pushpin, Slipstream, Solstice, Technic, Thatch, Trek, Urban, Verve, Waveform 306 | 307 | Office 2007 theme colors include: 308 | Apex, Aspect, Civic, Concourse, Equity, Flow, Foundry, Grayscale, Median, Metro, Module, Office, Opulent, Oriel, Origin, Paper, Solstice, Technic, Trek, Urban, Verve 309 | 310 | 311 | -ColorScheme 312 | Optional. The color theme to apply to each worksheet. Valid values are "Light", "Medium", and "Dark". If not specified then "Medium" is used as the default value . 313 | 314 | 315 | -LoggingPreference 316 | Optional. Specifies how much information will be written to a log file (location specified in the LogPath parameter). Valid values are None, Standard, Verbose, and Debug. The default value is None (i.e. no logging). 317 | 318 | 319 | -LogPath 320 | Optional. A literal path to a log file to write details about what this script is doing. The filename does not need to exist prior to executing this script but the specified directory does. 321 | 322 | If a LoggingPreference other than "None" is specified and this parameter is not provided then the file is named "SQL Server Inventory - [Year][Month][Day][Hour][Minute].log" and is written to the same directory specified by the ToDirectoryPath paramter. 323 | 324 | 325 | 326 | Examples 327 | 328 | The following examples demonstrate how to combine all the parameters together when running the script. 329 | 330 | Example 1: 331 | 332 | .\Convert-SqlServerInventoryClixmlToExcel.ps1 -FromPath "C:\Inventory\SQL Server Inventory.xml.gz" 333 | 334 | Writes Excel files for the Database Engine and Windows Operating System information contained in "C:\Inventory\SQL Server Inventory.xml.gz" to "C:\Inventory\SQL Server - Database Engine.xlsx" and "C:\Inventory\SQL Server - Windows.xlsx", respectively. 335 | 336 | The Office color theme and Medium color scheme will be used by default. 337 | 338 | 339 | Example 2: 340 | 341 | .\Convert-SqlServerInventoryClixmlToExcel.ps1 -FromPath "C:\Inventory\SQL Server Inventory.xml.gz" -ColorTheme Blue -ColorScheme Dark 342 | 343 | Writes Excel files for the Database Engine and Windows Operating System information contained in "C:\Inventory\SQL Server Inventory.xml.gz" to "C:\Inventory\SQL Server - Database Engine.xlsx" and "C:\Inventory\SQL Server - Windows.xlsx", respectively. 344 | 345 | The Blue color theme and Dark color scheme will be used. 346 | 347 | 348 | 349 | Additional Help 350 | --------------- 351 | If you're still having problems using SQL Power Doc after reading through 352 | this guide please post in the Discussions (https://sqlpowerdoc.codeplex.com/discussions) 353 | or reach out to @SQLDBA, @LarsPlatzdasch on Twitter. 354 | 355 | 356 | -------------------------------------------------------------------------------- /docs/WindowsInventory Readme.txt: -------------------------------------------------------------------------------- 1 | NOTE: Running a SQL Server Inventory will also perform a Windows Inventory! 2 | 3 | Requirements 4 | ------------- 5 | Before you can document your Windows environment with SQL Power Doc you'll to meet the following requirements: 6 | 7 | Permissions 8 | - The account used to run SQL Power Doc will need Administrator rights to the OS in order to collect information about the Operating System. 9 | 10 | Windows Machine To Perform Inventory 11 | - This can be virtual or physical - either way it's recommended that its located on the same physical network as the servers you are collecting information from. SQL Power Doc collects a lot of information and you don't want network latency to become a bottleneck 12 | 13 | - PowerShell likes memory; For documenting a 10-20 server environment you'll want at least 2 GB of RAM available. Logical CPU count isn't as important, but SQL Power Doc can split its workload across multiple CPUs when doing it's thing so the more CPUs you've got the faster the job will get done. 14 | 15 | - The following software needs to be installed: 16 | -- Windows PowerShell 2.0 or higher: 17 | Windows PowerShell 2.0 is available on on all Windows Operating Systems going back to Windows Server 2003 and Windows XP. Chances are you've already got it installed and enabled, but in case you're not sure head over to http://support.microsoft.com/kb/968929 for a list of requirements and instructions on how to get PowerShell working on your system. 18 | 19 | - Sometimes firewall and group policy restrictions will prevent SQL Power Doc from gathering information from servers. If that's your environment then you'll want to make sure to open up communications for both SQL Server and WMI (Windows Management Instrumentation). Start at http://msdn.microsoft.com/en-us/library/windows/desktop/aa822854(v=vs.85).aspx for instructions on how to do so. 20 | 21 | Windows Machine To Create The Documentation 22 | - Usually this will be your laptop or desktop 23 | 24 | - You'll need the following software installed: 25 | -- Windows PowerShell 2.0 or higher 26 | -- Microsoft Excel 2007 or higher 27 | 28 | You can use the same machine to collect an inventory and create the documentation as long as it meets all the requirements outlined above. 29 | 30 | 31 | 32 | Configure Windows PowerShell 33 | ---------------------------- 34 | You'll want to make sure that PowerShell is configured properly on both the machine that you're using to perform the inventory and the machine that's building the documentation. (Repeat: Do this on both machines!) 35 | 36 | Set Execution Policy 37 | By default PowerShell tries to keep you from shooting yourself in the foot by not letting you run scripts that you download from the internet. In PowerShell lingo this is referred to as the Execution Policy (see http://technet.microsoft.com/en-us/library/hh847748.aspx) and in order to use SQL Power Doc you'll need to change it by following these steps: 38 | 1.Open a PowerShell console in elevated mode: 39 | Start -> All Programs -> Accessories -> Windows PowerShell -> Windows PowerShell (right click, choose "Run as Administrator") 40 | 41 | 2.Set the execution policy to allow for remotely signed scripts 42 | Set-ExecutionPolicy RemoteSigned -Force 43 | 44 | 3.Exit the PowerShell console 45 | 46 | Configure Windows PowerShell Directory 47 | Now you'll need to create a directory to hold PowerShell code. 48 | 1.Open a new PowerShell console (but not in elevated mode as when you set the execution policy): 49 | Start -> All Programs -> Accessories -> Windows PowerShell -> Windows PowerShell 50 | 51 | 2.Create PowerShell and PowerShell modules directory in your "My Documents" folder 52 | New-Item -type directory -path "$([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))\WindowsPowerShell\Modules" 53 | 54 | 3.Exit the PowerShell console 55 | 56 | 57 | Download And Install 58 | -------------------- 59 | Grab the latest version of the code from the downloads page but don't extract the ZIP file yet! Because the file came from the internet it needs to be unblocked or PowerShell gets cranky because it's considered untrusted. 60 | 61 | To unblock a file, navigate to it in Windows Explorer, right click, and choose the Properties menu option. On the General tab, click the Unblock button, then click the OK button to close the Properties dialog. 62 | 63 | Once the file is unblocked you can extract the contents to the WindowsPowerShell folder (in your "My Documents" directory) that you created in the last step. 64 | 65 | Note: Make sure to keep the folder names in the zip file intact so that everything in the Modules folder is extracted into WindowsPowerShell\Modules and the .ps1 files are extracted into the WindowsPowerShell folder. 66 | 67 | The Windows Inventory portion of SQL Power Doc will attempt to use the RDS-Manager PowerShell module provided by the Microsoft Remote Desktop Services team. You can download this optional module from http://gallery.technet.microsoft.com/ScriptCenter/e8c3af96-db10-45b0-88e3-328f087a8700/ . Make sure to save it in the WindowsPowerShell\Modules\RDS-Manager folder (in your "My Documents" directory) that you recently created. It's not the end of the world if it's missing but you'll get more details about users' desktop sessions when it's installed. 68 | 69 | 70 | Collect Windows Inventory 71 | ------------------------------------------- 72 | So far, so good...now it's time to discover your Windows Servers and perform an inventory! In this step you're going to run a PowerShell script which will discover Windows machines on your network (or verify they're running), collect information about them and their underlying OS, and write the results as an XML file that you'll use in the next step. 73 | 74 | Start by opening a PowerShell console on the machine that will be collecting the information from your SQL Servers and set your current location to the WindowsPowerShell folder: 75 | Set-Location "$([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))\WindowsPowerShell" 76 | 77 | Running The Script, Choosing The Right Parameters 78 | 79 | You're going to execute the script .\Get-WindowsInventoryToClixml.ps1 to do all the work but it requires a few parameters to know what to do. 80 | 81 | Discover & Verify Windows Machines 82 | 83 | The first set of parameters define how to find & verify Windows machines. 84 | 85 | Find Windows machines by querying Active Directory DNS for hosts 86 | 87 | -DnsServer 88 | Valid values are "automatic" or a comma delimited list of AD DNS server IP addresses to query for DNS A records that may be Windows machines. 89 | 90 | -DnsDomain 91 | Optional. Valid values are "automatic" (the default if this parameter is not specified) or the AD domain name to query for DNS records from. 92 | 93 | -ExcludeSubnet 94 | Optional. This is a comma delimited list of CIDR notation subnets to exclude when looking for Windows machines. 95 | 96 | -LimitSubnet 97 | Optional. This is an inclusive comma delimited list of CIDR notation subnets to limit the scope when looking for Windows machines. 98 | 99 | -ExcludeComputerName 100 | Optional. This is a comma delimited list of computer names to exclude when looking for Windows machines. 101 | 102 | -PrivateOnly 103 | Optional. This switch limits the scope to private class A, B, and C IP addresses when looking for Windows machines. 104 | 105 | 106 | Find Windows machines by scanning a subnet of IP Addresses 107 | 108 | -Subnet 109 | Valid values are "automatic" or a comma delimited list of CIDR notation subnets to scan for IPv4 hosts that may be Windows machines. 110 | 111 | -LimitSubnet 112 | Optional. This is an inclusive comma delimited list of CIDR notation subnets to limit the scope when looking for Windows machines. 113 | 114 | -ExcludeComputerName 115 | Optional. This is a comma delimited list of computer names to exclude when looking for Windows machines. 116 | 117 | -PrivateOnly 118 | Optional. This switch limits the scope to private class A, B, and C IP addresses when looking for Windows machines. 119 | 120 | 121 | Find Windows machines by computer name 122 | 123 | -ComputerName 124 | This is a comma delimited list of computer names that may be Windows machines. 125 | 126 | -PrivateOnly 127 | Optional. This switch limits the scope to private class A, B, and C IP addresses when looking for Windows machines. 128 | 129 | 130 | Additional Information To Collect 131 | 132 | By default, SQL Power Doc collects a minimal set of information about each Windows machine it finds. You can collect more information with the following parameter: 133 | 134 | -AdditionalData 135 | This is a comma delimited list of one or more of the following additional data points to collect: 136 | AdditionalHardware 137 | BIOS 138 | DesktopSessions 139 | EventLog 140 | FullyQualifiedDomainName 141 | InstalledApplications 142 | InstalledPatches 143 | IPRoutes 144 | LastLoggedOnUser 145 | LocalGroups 146 | LocalUserAccounts 147 | PowerPlans 148 | Printers 149 | PrintSpoolerLocation 150 | Processes 151 | ProductKeys 152 | RegistrySize 153 | Services 154 | Shares 155 | StartupCommands 156 | WindowsComponents 157 | 158 | Alternatively, you can specify the value "All" to include all of the data points listed above. 159 | 160 | If this parameter is not provided the default is that none of the listed data points will be included. 161 | 162 | 163 | Logging, Output, & Resource Utilization 164 | 165 | Finally, the following parameters control logging, output, and system resources SQL Power Doc will use when finding and collecting information from Windows machines: 166 | 167 | -DirectoryPath 168 | Optional. A fully qualified directory path where all output will be written. The default value is your "My Documents" folder. 169 | 170 | -LoggingPreference 171 | Optional. Specifies how much information will be written to a log file (in the same directory as the output file). Valid values are None, Standard, Verbose, and Debug. The default value is None (i.e. no logging). 172 | 173 | -Zip 174 | Optional. If provided, create a Zip file containing all output in the directory specified by the DirectoryPath parameter. 175 | 176 | -MaxConcurrencyThrottle 177 | Optional. A number between 1-100 which indicates how many tasks to perform concurrently. The default is the number of logical CPUs present on the OS. 178 | 179 | 180 | Examples 181 | 182 | The following examples demonstrate how to combine all the parameters together when running the script. 183 | 184 | Example 1: 185 | 186 | .\Get-WindowsInventoryToClixml.psm1 -DNSServer automatic -DNSDomain automatic -PrivateOnly 187 | 188 | Collect an inventory by querying Active Directory for a list of hosts to scan for Windows machines. The list of hosts will be restricted to private IP addresses only. 189 | 190 | The Inventory file will be written to your "My Documents" folder. 191 | 192 | No Log file will be written. 193 | 194 | 195 | Example 2: 196 | 197 | .\Get-WindowsInventoryToClixml.psm1 -Subnet 172.20.40.0/28 -LoggingPreference Standard 198 | 199 | Collect an inventory by scanning all hosts in the subnet 172.20.40.0/28 for Windows machines. 200 | 201 | The Inventory and Log files will be written to your "My Documents" folder. 202 | 203 | Standard logging will be used. 204 | 205 | 206 | Example 3: 207 | 208 | .\Get-WindowsInventoryToClixml.psm1 -Computername Server1,Server2,Server3 209 | 210 | Collect an inventory by scanning Server1, Server2, and Server3 for Windows machines. 211 | 212 | The Inventory file will be written to your "My Documents" folder. 213 | 214 | No Log file will be written. 215 | 216 | 217 | Example 4: 218 | 219 | .\Get-WindowsInventoryToClixml.psm1 -Computername $env:COMPUTERNAME -AdditionalData None -LoggingPreference Verbose 220 | 221 | Collect an inventory by scanning the local machine for Windows machines. 222 | 223 | Do not collect any data beyond the core set of information. 224 | 225 | The Inventory and Log files will be written to your "My Documents" folder. 226 | 227 | Verbose logging will be used. 228 | 229 | 230 | 231 | How Long Will It Take? 232 | ---------------------- 233 | When run with the defaults on a machine with 2 CPUs you can expect the script to take about 5 minutes to complete an inventory of 20 machines. Your mileage will vary depending on how many machines you are collecting information from and what additional information you're including. 234 | 235 | Progress is written to the PowerShell console to give you a better idea what the script's up to. If you've got logging enabled (highly recommended) you can also check the logs for progress updates. 236 | 237 | 238 | 239 | Generate A Windows Inventory Report 240 | ------------------------------- 241 | Once the inventory collection phase is complete you'll want to copy the output file to the machine where you'll create the inventory reports (Excel workbooks). 242 | 243 | To create an inventory report, start by opening a PowerShell console and set your current location to the WindowsPowerShell folder: 244 | Set-Location "$([Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))\WindowsPowerShell" 245 | 246 | This time you're going to execute the script .\Convert-WindowsInventoryClixmlToExcel.ps1 and supply the following parameters: 247 | 248 | -FromPath 249 | 250 | The literal path to the output file created by Get-WindowsInventoryToClixml.ps1. 251 | 252 | -ToDirectoryPath 253 | Optional. Specifies the literal path to the directory where the Excel workbooks will be written. This path must exist prior to executing the script. If this parameter is not provided the workbooks will be written to the same directory specified in the FromPath parameter. Assuming the XML file specified in FromPath is named "Windows Inventory.xml" then the Excel file will be written to "Windows Inventory.xlsx". 254 | 255 | 256 | -ColorTheme 257 | Optional. An Office Theme Color to apply to each worksheet. If not specified or if an unknown theme color is provided the default "Office" theme colors will be used. 258 | 259 | Office 2013 theme colors include: 260 | Aspect, Blue Green, Blue II, Blue Warm, Blue, Grayscale, Green Yellow, Green, Marquee, Median, Office, Office 2007 - 2010, Orange Red, Orange, Paper, Red Orange, Red Violet, Red, Slipstream, Violet II, Violet, Yellow Orange, Yellow 261 | 262 | Office 2010 theme colors include: 263 | Adjacency, Angles, Apex, Apothecary, Aspect, Austin, Black Tie, Civic, Clarity, Composite, Concourse, Couture, Elemental, Equity, Essential, Executive, Flow, Foundry, Grayscale, Grid, Hardcover, Horizon, Median, Metro, Module, Newsprint, Office, Opulent, Oriel, Origin, Paper, Perspective, Pushpin, Slipstream, Solstice, Technic, Thatch, Trek, Urban, Verve, Waveform 264 | 265 | Office 2007 theme colors include: 266 | Apex, Aspect, Civic, Concourse, Equity, Flow, Foundry, Grayscale, Median, Metro, Module, Office, Opulent, Oriel, Origin, Paper, Solstice, Technic, Trek, Urban, Verve 267 | 268 | 269 | -ColorScheme 270 | Optional. The color theme to apply to each worksheet. Valid values are "Light", "Medium", and "Dark". If not specified then "Medium" is used as the default value . 271 | 272 | 273 | -LoggingPreference 274 | Optional. Specifies how much information will be written to a log file (location specified in the LogPath parameter). Valid values are None, Standard, Verbose, and Debug. The default value is None (i.e. no logging). 275 | 276 | 277 | -LogPath 278 | Optional. A literal path to a log file to write details about what this script is doing. The filename does not need to exist prior to executing this script but the specified directory does. 279 | 280 | If a LoggingPreference other than "None" is specified and this parameter is not provided then the file is named "SQL Server Inventory - [Year][Month][Day][Hour][Minute].log" and is written to the same directory specified by the ToDirectoryPath paramter. 281 | 282 | 283 | 284 | Examples 285 | 286 | The following examples demonstrate how to combine all the parameters together when running the script. 287 | 288 | Example 1: 289 | 290 | .\Convert-WindowsInventoryClixmlToExcel.ps1 -FromPath "C:\Inventory\Windows Inventory.xml" 291 | 292 | Writes an Excel file for the Windows Operating System information contained in "C:\Inventory\Windows Inventory.xml" to "C:\Inventory\Windows Inventory.xlsx". 293 | 294 | The Office color theme and Medium color scheme will be used by default. 295 | 296 | 297 | Example 2: 298 | 299 | .\Convert-WindowsInventoryClixmlToExcel.ps1 -FromPath "C:\Inventory\Windows Inventory.xml" -ColorTheme Blue -ColorScheme Dark 300 | 301 | Writes an Excel file for the Windows Operating System information contained in "C:\Inventory\Windows Inventory.xml" to "C:\Inventory\Windows Inventory.xlsx". 302 | 303 | The Blue color theme and Dark color scheme will be used. 304 | 305 | 306 | 307 | Additional Help 308 | --------------- 309 | If you're still having problems using SQL Power Doc after reading through this 310 | guide please post in the Discussions (https://sqlpowerdoc.codeplex.com/discussions) 311 | or reach out to @SQLDBA, @LarsPlatzdasch on Twitter. 312 | 313 | 314 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: Welcome to the Octocat 3 | description: Merp -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Hi -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>SheepReaper/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /requirements.md: -------------------------------------------------------------------------------- 1 | # Requirements for SQLPowerShell DOC 2 | 3 | ### Requirements 4 | 5 | - RSAT Tools Windows 7...10 6 | - PowerShell min 4 best 5++ 7 | - SSMS SQL Server Management Studio / Sql Server Management Objects (SMO) 8 | 9 | ### Login 10 | * with SQL Server SYSADMIN Rights 11 | 12 | [more you find here](../../wiki) 13 | 14 | 15 | --------------------------------------------------------------------------------