Today I'm pushing forward with PSWinDocumentation project. I've fixed some bugs but I also added a couple of new features. I did lie a bit in the first sentence because this time it's not all me. I got help from Mateusz Niemczyk who is a certified AWS engineer working for Euvic with me on some projects. If you've not yet guessed where I got him involved from the introduction – yes we're adding basic AWS data support to PSWinDocumentation. But that's not all…
This version has 3 things that differentiate it from the old version
While it's far from complete it's a starting point (feel free to open feature requests on GitHub – he did promise to add more if/when needed). It covers 5 types of data
Let's have a quick look how at it…
Of course all that data is available as Word, Excel or SQL or data object. To get that data-object you work with the code like below
Import-Module AWSPowerShell Import-Module PSSharedGoods Import-Module PSWinDocumentation $Configuration = [ordered] @{ AWSAccessKey = '' AWSSecretKey = '' AWSRegion = '' } $AWS = Get-WinAWSInformation -AWSAccessKey $Configuration.AWSAccessKey -AWSSecretKey $Configuration.AWSSecretKey -AWSRegion $Configuration.AWSRegion $AWS.AWSEC2Details | Format-Table -AutoSize $AWS.AWSRDSDetails | Format-Table -AutoSize $AWS.AWSLBDetails | Format-Table -AutoSize $AWS.AWSNetworkDetails | Format-Table -AutoSize $AWS.AWSEIPDetails | Format-Table -AutoSize $AWS.AWSIAMDetails | Format-Table -AutoSize
This covers basics of AWS. If you feel like it's missing some data, or that it would be useful for others to have more documentation on AWS front feel free to shout!
If you want to run AWS documentation… following starting script will do it for you. Keep in mind this and other examples on how to use PSWinDocumentation module are located on GitHub in Examples. Keep in mind that what you see in sources may be higher than published version.
Import-Module PSWinDocumentation Import-Module AWSPowerShell # Import-Module DbaTools # (if you require SQL export) # Those should automatically load #Import-Module PSWriteWord #Import-Module PSWriteExcel $Document = [ordered]@{ Configuration = [ordered] @{ Prettify = @{ CompanyName = 'Evotec' UseBuiltinTemplate = $true CustomTemplatePath = '' Language = 'en-US' } Options = @{ OpenDocument = $true OpenExcel = $true } DisplayConsole = @{ ShowTime = $false LogFile = "$ENV:TEMP\PSWinDocumentationTesting.log" TimeFormat = 'yyyy-MM-dd HH:mm:ss' } Debug = @{ Verbose = $false } } DocumentAWS = [ordered] @{ Enable = $true ExportWord = $true ExportExcel = $true ExportSql = $false FilePathWord = "$Env:USERPROFILE\Desktop\PSWinDocumentation-ReportAWS.docx" FilePathExcel = "$Env:USERPROFILE\Desktop\PSWinDocumentation-ReportAWS.xlsx" Configuration = [ordered] @{ AWSAccessKey = '' AWSSecretKey = '' AWSRegion = '' } Sections = [ordered] @{ SectionTOC = [ordered] @{ Use = $true TocGlobalDefinition = $true TocGlobalTitle = 'Table of content' TocGlobalRightTabPos = 15 #TocGlobalSwitches = 'A', 'C' #[TableContentSwitches]::C, [TableContentSwitches]::A PageBreaksAfter = 1 } SectionAWSIntroduction = [ordered] @{ ### Enables section Use = $true ### Decides how TOC should be visible TocEnable = $True TocText = 'Scope' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 ### Text is added before table/list Text = "This document provides starting overview of AWS..." TextAlignment = [Alignment]::Both PageBreaksAfter = 1 } SectionEC2 = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - EC2' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.AWS]::AWSEC2Details TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "Basic information about EC2 servers such as ID, name, environment, instance type and IP." ExcelExport = $true ExcelWorkSheet = 'AWSEC2Details' ExcelData = [PSWinDocumentation.AWS]::AWSEC2Details SqlExport = $true SqlServer = 'EVO1' SqlDatabase = 'SSAE18' SqlData = [PSWinDocumentation.AWS]::AWSEC2Details SqlTable = 'dbo.[AWSEC2Details]' SqlTableCreate = $true } SectionRDS = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - RDS Details' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.AWS]::AWSRDSDetails TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "Basic information about RDS databases such as name, class, mutliAZ, engine version." ExcelExport = $true ExcelWorkSheet = 'AWSRDSDetails' ExcelData = [PSWinDocumentation.AWS]::AWSRDSDetails } SectionELB = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - Load Balancers' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.AWS]::AWSLBDetails TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "Basic information about ELB and ALB such as name, DNS name, targets, scheme." ExcelExport = $true ExcelWorkSheet = 'AWSLBDetailsList' ExcelData = [PSWinDocumentation.AWS]::AWSLBDetails } SectionVPC = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - Networking' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.AWS]::AWSSubnetDetails TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "Basic information about subnets such as: id, name, CIDR, free IP and VPC." ExcelExport = $true ExcelWorkSheet = 'AWSSubnetDetails' ExcelData = [PSWinDocumentation.AWS]::AWSSubnetDetails } SectionEIP = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - Elastic IPs' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.AWS]::AWSElasticIpDetails TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "Basic information about reserved elastic IPs such as name, IP, network interface." ExcelExport = $true ExcelWorkSheet = 'AWSElasticIpDetails' ExcelData = [PSWinDocumentation.AWS]::AWSElasticIpDetails } SectionIAM = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - IAM Users' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.AWS]::AWSIAMDetails TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "Basic information about IAM users such as groups and MFA details." ExcelExport = $true ExcelWorkSheet = 'AWSIAMDetails' ExcelData = [PSWinDocumentation.AWS]::AWSIAMDetails } } } } Start-Documentation -Document $Document -Verbose
While I don't think it will be very popular feature it was added and it is there to stay. Basically it works the same way that export to excel or word works. It's able to create new table if it doesn't exists in database, alter table if for example new fields in data will show up, and finally it can work via mapping of columns.
SectionForestSummary = [ordered] @{ Use = $false TocEnable = $True TocText = 'General Information - Forest Summary' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.ActiveDirectory]::ForestInformation TableDesign = [TableDesign]::ColorfulGridAccent5 TableTitleMerge = $true TableTitleText = "Forest Summary" Text = "Active Directory at <CompanyName> has a forest name <ForestName>." ` + " Following table contains forest summary with important information:" ExcelExport = $true ExcelWorkSheet = 'Forest Summary' ExcelData = [PSWinDocumentation.ActiveDirectory]::ForestInformation SqlExport = $true SqlServer = 'EVO1' SqlDatabase = 'SSAE18' SqlData = [PSWinDocumentation.ActiveDirectory]::ForestInformation SqlTable = 'dbo.[ForestInformation]' SqlTableTranspose = $true SqlTableCreate = $true }
In example above one section defines 3 exports – to word, excel and sql at same time. You can just define Sql stuff and remove rest thou as shown below
SectionExcelDomainUsersNeverExpiring = [ordered] @{ Use = $true SqlExport = $true SqlServer = 'EVO1' SqlDatabase = 'SSAE18' SqlTableCreate = $true SqlTableAlterIfNeeded = $true SqlData = [PSWinDocumentation.ActiveDirectory]::DomainUsersNeverExpiring SqlTable = 'dbo.[UsersNeverExpiring]' }
It works for any data type so feel free to experiment. You can also use it with Table Mapping defined just in case you don't want to allow anything go unnoticed.
SectionExcelDomainUsers = [ordered] @{ Use = $false ExcelExport = $false ExcelWorkSheet = '<Domain> - Users' ExcelData = [PSWinDocumentation.ActiveDirectory]::DomainUsers SqlExport = $true SqlServer = 'EVO1' SqlDatabase = 'SSAE18' SqlData = [PSWinDocumentation.ActiveDirectory]::DomainUsers SqlTable = 'dbo.[Users]' SqlTableCreate = $true DisabledSqlTableMapping = [ordered] @{ # SqlTableMapping is proper name # Left Side is data in PSWinReporting # Right Side is column name in SQL # Changing makes sense only for left side... # Use this if you need to have different mapping 'Name' = 'Name' 'UserPrincipalName' = 'UserPrincipalName' 'SamAccountName' = 'SamAccountName' 'Display Name' = 'Display Name' 'Given Name' = 'Given Name' 'Surname' = 'Surname' 'EmailAddress' = 'EmailAddress' 'PasswordExpired' = 'PasswordExpired' 'PasswordLastSet' = 'PasswordLastSet' 'PasswordNotRequired' = 'PasswordNotRequired' 'PasswordNeverExpires' = 'PasswordNeverExpires' 'Enabled' = 'Enabled' 'Manager' = 'Manager' 'Manager Email' = 'Manager Email' 'DateExpiry' = 'DateExpiry' 'DaysToExpire' = 'DaysToExpire' 'AccountExpirationDate' = 'AccountExpirationDate' 'AccountLockoutTime' = 'AccountLockoutTime' 'AllowReversiblePasswordEncryption' = 'AllowReversiblePasswordEncryption' 'BadLogonCount' = 'BadLogonCount' 'CannotChangePassword' = 'CannotChangePassword' 'CanonicalName' = 'CanonicalName' 'Description' = 'Description' 'DistinguishedName' = 'DistinguishedName' 'EmployeeID' = 'EmployeeID' 'EmployeeNumber' = 'EmployeeNumber' 'LastBadPasswordAttempt' = 'LastBadPasswordAttempt' 'LastLogonDate' = 'LastLogonDate' 'Created' = 'Created' 'Modified' = 'Modified' 'Protected' = 'Protected' 'Primary Group' = 'Primary Group' 'Member Of' = 'Member Of' 'AddedWhen' = 'AddedWhen'# ColumnsToTrack when it was added to database and by who / not part of event 'AddedWho' = 'AddedWho' # ColumnsToTrack when it was added to database and by who / not part of event } }
Just so we're clear here… it's very basic and it's very start of Exchange, Office 365 data sets. I made it so that you could throw in ideas on what proper documentation for each service should contain. I do plan to add Teams, Skype and other Office 365 services support. I've not made extensive testing on connecting, reusing connections or working with MFA (I expect it to not work actually at the moment). This is all planned and will be fixed within next few weeks. There is one more thing here that is planned and didn't make it for 0.2 version – support for Export-CliXML. Generally when working with large services like Office 365, Exchange, AD that sometimes throttle you when you try to get all data it's not really efficient to work on LIVE data all the time. So the plan for next release is to allow script run once, export data to XML and on subsequent retries when you will be reworking the way your documentation looks like it will not ask Office 365 again, and you won't have to wait 3 hours to get data you need. But for now… you can either use it as is or skip this part until next version.
Import-Module PSWinDocumentation -Force Import-Module PSSharedGoods -Force $session = Connect-Exchange -ConnectionURI 'http://ex2013x3.ad.evotec.xyz/Powershell' -SessionName 'Evotec' -Verbose $Test = Import-PSSession -Session $Session -AllowClobber -DisableNameChecking $Exchange = Get-WinExchangeInformation -Verbose $Exchange | ft -a
There is one more thing to know here. Each data sets starts with O365 or Exchange. Then if there's U for example ExchangeUServers it basically means it returns all data provided by command and usually has not been processed by me to prepare it for Word output.
$Data.ExchangeUServers = Get-ExchangeServer
However if there's no U in name it means output was processed and kind of provides some specific data and is more or less Word friendly.
$Data.ExchangeServers = Invoke-Command -ScriptBlock { $Servers = @() foreach ($Server in $Data.ExchangeUServers) { $Servers += [PSCustomObject]@{ Name = $Server.Name Roles = $Server.ServerRole Edition = $Server.Edition Version = $Server.AdminDisplayVersion Trial = $Server.IsExchangeTrialEdition FQDN = $Server.FQDN } } return $Servers }
As you see above ExchangeServers has stricly defined data. You can also treat this as a guide on how easy it's to add new data to data types already defined. You prepare something like above and add it to Get-WinExchangeInformation.ps1, add name of it to Enums\Exchange.ps1 and that's it. After restarting PowerShell session it's basically available as part of data set. Therefore if you have skills feel free to either make PR on GitHub or if you don't want to play with PR's just open an issue and paste your code there. If it will be working and more or less good enough for this I'll add it.
This output can be generated with following code. You can work with that HashTable / OrderedDictionary object in any way you want. But like I said earlier it's just a starting point and something that will have to grow in next few weeks.
Import-Module PSWinDocumentation -Force Import-Module PSSharedGoods -Force $O365 = Get-WinO365 -Verbose $O365
If you would like to run Word/Excel/SQL export on Office 365 you can try it with following starter pack
Import-Module PSWinDocumentation -Verbose -Force Import-Module PSWinDocumentation.O365 $Document = [ordered]@{ Configuration = [ordered] @{ Prettify = @{ CompanyName = 'Evotec' UseBuiltinTemplate = $true CustomTemplatePath = '' Language = 'en-US' } Options = @{ OpenDocument = $true OpenExcel = $true } DisplayConsole = @{ ShowTime = $false LogFile = "$ENV:TEMP\PSWinDocumentationTesting.log" TimeFormat = 'yyyy-MM-dd HH:mm:ss' } Debug = @{ Verbose = $false } } DocumentOffice365 = [ordered] @{ Enable = $true ExportWord = $true ExportExcel = $true FilePathWord = "$Env:USERPROFILE\Desktop\PSWinDocumentation-ReportO365.docx" FilePathExcel = "$Env:USERPROFILE\Desktop\PSWinDocumentation-ReportO365.xlsx" Configuration = [ordered] @{ O365ExchangeUse = $true O365AzureADUse = $true O365Username = 'przemyslaw.klys@evotec.pl' O365Password = 'C:\Support\Important\Password-O365-Evotec.txt' O365PasswordAsSecure = $true O365PasswordFromFile = $true O365ExchangeSessionName = 'O365ExchangeOnline' O365ExchangeAuthentication = 'Basic' O365ExchangeURI = 'https://outlook.office365.com/powershell-liveid/' O365AzureSessionName = 'O365Azure' } Sections = [ordered] @{ SectionO365TOC = [ordered] @{ Use = $true TocGlobalDefinition = $true TocGlobalTitle = 'Table of content' TocGlobalRightTabPos = 15 #TocGlobalSwitches = 'A', 'C' #[TableContentSwitches]::C, [TableContentSwitches]::A PageBreaksAfter = 0 } SectionO365Introduction = [ordered] @{ ### Enables section Use = $true ### Decides how TOC should be visible TocEnable = $True TocText = 'Scope' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 ### Text is added before table/list Text = "" TextAlignment = [Alignment]::Both PageBreaksAfter = 1 } SectionO365ExchangeMailBoxes = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - O365ExchangeMailBoxes' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.O365]::UExchangeMailBoxes TableDesign = [TableDesign]::ColorfulGridAccent5 Text = "This is test" ExcelExport = $false ExcelWorkSheet = 'O365ExchangeMailBoxes' ExcelData = [PSWinDocumentation.O365]::UExchangeMailBoxes } O365AzureTenantDomains = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - Office 365 Domains' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.O365]::AzureTenantDomains TableMaximumColumns = 7 TableDesign = [TableDesign]::ColorfulGridAccent5 Text = 'Following table contains all domains defined in Office 365 portal.' ExcelExport = $true ExcelWorkSheet = 'O365 Domains' ExcelData = [PSWinDocumentation.O365]::AzureTenantDomains } O365AzureADGroupMembersUser = [ordered] @{ Use = $true TocEnable = $True TocText = 'General Information - O365AzureADGroupMembersUser' TocListLevel = 0 TocListItemType = [ListItemType]::Numbered TocHeadingType = [HeadingType]::Heading1 TableData = [PSWinDocumentation.O365]::AzureADGroupMembersUser TableDesign = [TableDesign]::ColorfulGridAccent5 ExcelExport = $true ExcelWorkSheet = 'O365AzureADGroupMembersUser' ExcelData = [PSWinDocumentation.O365]::AzureADGroupMembersUser } ## Data below makes sense only in Excel / SQL Export O365AzureLicensing = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureLicensing' ExcelData = [PSWinDocumentation.O365]::UAzureLicensing } O365AzureSubscription = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureSubscription' ExcelData = [PSWinDocumentation.O365]::UAzureSubscription } O365AzureADUsers = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureADUsers' ExcelData = [PSWinDocumentation.O365]::UAzureADUsers } O365AzureADUsersDeleted = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureADUsersDeleted' ExcelData = [PSWinDocumentation.O365]::UAzureADUsersDeleted } O365AzureADGroups = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureADGroups' ExcelData = [PSWinDocumentation.O365]::UAzureADGroups } O365AzureADGroupMembers = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureADGroupMembers' ExcelData = [PSWinDocumentation.O365]::UAzureADGroupMembers } O365AzureADContacts = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365AzureADContacts' ExcelData = [PSWinDocumentation.O365]::UAzureADContacts } O365ExchangeMailBoxes = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeMailBoxes' ExcelData = [PSWinDocumentation.O365]::UExchangeMailBoxes } O365ExchangeMailUsers = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeMailUsers' ExcelData = [PSWinDocumentation.O365]::UExchangeMailUsers } O365ExchangeRecipientsPermissions = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeRecipientsPermissions' ExcelData = [PSWinDocumentation.O365]::UExchangeRecipientsPermissions } O365ExchangeGroupsDistributionDynamic = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeGroupsDistributionDynamic' ExcelData = [PSWinDocumentation.O365]::UExchangeGroupsDistributionDynamic } O365ExchangeMailboxesEquipment = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeMailboxesEquipment' ExcelData = [PSWinDocumentation.O365]::UExchangeMailboxesEquipment } O365ExchangeUsers = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeUsers' ExcelData = [PSWinDocumentation.O365]::UExchangeUsers } O365ExchangeMailboxesRooms = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeMailboxesRooms' ExcelData = [PSWinDocumentation.O365]::UExchangeMailboxesRooms } O365ExchangeGroupsDistributionMembers = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeGroupsDistributionMembers' ExcelData = [PSWinDocumentation.O365]::UExchangeGroupsDistributionMembers } O365ExchangeEquipmentCalendarProcessing = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeEquipmentCalendarProcessing' ExcelData = [PSWinDocumentation.O365]::UExchangeEquipmentCalendarProcessing } O365ExchangeGroupsDistribution = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeGroupsDistribution' ExcelData = [PSWinDocumentation.O365]::UExchangeGroupsDistribution } O365ExchangeContactsMail = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeContactsMail' ExcelData = [PSWinDocumentation.O365]::UExchangeContactsMail } O365ExchangeMailboxesJunk = [ordered] @{ Use = $false ExcelExport = $true ExcelWorkSheet = 'O365ExchangeMailboxesJunk' ExcelData = [PSWinDocumentation.O365]::UExchangeMailboxesJunk } O365ExchangeRoomsCalendarPrcessing = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeRoomsCalendarPrcessing' ExcelData = [PSWinDocumentation.O365]::UExchangeRoomsCalendarPrcessing } O365ExchangeContacts = [ordered] @{ Use = $true ExcelExport = $true ExcelWorkSheet = 'O365ExchangeContacts' ExcelData = [PSWinDocumentation.O365]::UExchangeContacts } } } } Start-Documentation -Document $Document -Verbose
It's not entirely feature of PSWinDocumentation but more of PSWriteExcel or Export to SQL (this feature is not available in PSWriteWord as it doesn't make sense). But what is it? Well you know when you run following command
$AllUsers = Get-ADUser -Filter * -Properties * | Select-Object *
You get all (or most of it) Active Directory holds for users. The trick is each object within $AllUsers can have different fields and it will only return those which are set. This means that first object can have 50 properties, 2nd object 100 and 3rd object 90. Not at all the same properties in all cases. While in normal case it doesn't really matter when you export to Excel or export to SQL you need to know before hand all properties to create table or to make sure they are always returned in same order. The prescanning feature does just that. It scans $AllUsers for all properties returned by each and every object and adds them up creating one big list. In old version without prescanning this module would take only 1st object, extract it's properties and based on that build Headers, Tables. This would mean if first object didn't contain ExtensionAttribute1 to 15 you wouldn't get them on subsequent objects. Prescanning solves this. The order of columns may be a bit off but it's well worth it.