{"id":373,"date":"2019-08-11T18:18:29","date_gmt":"2019-08-12T00:18:29","guid":{"rendered":"http:\/\/draith.com\/?p=373"},"modified":"2019-08-11T18:18:29","modified_gmt":"2019-08-12T00:18:29","slug":"powershell-what-modules-are-in-use","status":"publish","type":"post","link":"https:\/\/draith.com\/?p=373","title":{"rendered":"Powershell &#8211; what modules are in use?"},"content":{"rendered":"\n\n\n<p>There comes a time when you are neck deep in code, and come to the realization that one of your modules could really use a tweak.\u00a0 There is just one problem &#8211; what other scripts are using this module?\u00a0 What if you wanted to inventory your scripts and the modules that each script is using?\u00a0 This was the main use-case for this post.\u00a0 I have a scripts directory, and want to get a quick inventory of what modules each one is using.\u00a0\u00a0<\/p>\n<p>First, the function:<\/p>\n<pre class=\"lang:ps decode:true\">#requires -version 3.0\nFunction Test-ScriptFile {\n    &lt;#\n    .Synopsis\n    Test a PowerShell script for cmdlets\n    .Description\n    This command will analyze a PowerShell script file and display a list of detected commands such as PowerShell cmdlets and functions. Commands will be compared to what is installed locally. It is recommended you run this on a Windows 8.1 client with the latest version of RSAT installed. Unknown commands could also be internally defined functions. If in doubt view the contents of the script file in the PowerShell ISE or a script editor.\n    You can test any .ps1, .psm1 or .txt file.\n    .Parameter Path\n    The path to the PowerShell script file. You can test any .ps1, .psm1 or .txt file.\n    .Example\n    PS C:\\&gt; test-scriptfile C:\\scripts\\Remove-MyVM2.ps1\n     \n    CommandType Name                                   ModuleName\n    ----------- ----                                   ----------\n        Cmdlet Disable-VMEventing                      Hyper-V\n        Cmdlet ForEach-Object                          Microsoft.PowerShell.Core\n        Cmdlet Get-VHD                                 Hyper-V\n        Cmdlet Get-VMSnapshot                          Hyper-V\n        Cmdlet Invoke-Command                          Microsoft.PowerShell.Core\n        Cmdlet New-PSSession                           Microsoft.PowerShell.Core\n        Cmdlet Out-Null                                Microsoft.PowerShell.Core\n        Cmdlet Out-String                              Microsoft.PowerShell.Utility\n        Cmdlet Remove-Item                             Microsoft.PowerShell.Management\n        Cmdlet Remove-PSSession                        Microsoft.PowerShell.Core\n        Cmdlet Remove-VM                               Hyper-V\n        Cmdlet Remove-VMSnapshot                       Hyper-V\n        Cmdlet Write-Debug                             Microsoft.PowerShell.Utility\n        Cmdlet Write-Verbose                           Microsoft.PowerShell.Utility\n        Cmdlet Write-Warning                           Microsoft.PowerShell.Utility\n\n    .Notes\n    Original script provided by Jeff Hicks at (https:\/\/www.petri.com\/powershell-problem-solver-find-script-commands)\n\n    #&gt;\n     \n    [cmdletbinding()]\n    Param(\n        [Parameter(Position = 0, Mandatory = $True, HelpMessage = \"Enter the path to a PowerShell script file,\",\n            ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]\n        [ValidatePattern( \"\\.(ps1|psm1|txt)$\")]\n        [ValidateScript( { Test-Path $_ })]\n        [string]$Path\n    )\n     \n    Begin {\n        Write-Verbose \"Starting $($MyInvocation.Mycommand)\"  \n        Write-Verbose \"Defining AST variables\"\n        New-Variable astTokens -force\n        New-Variable astErr -force\n    }\n     \n    Process {\n        Write-Verbose \"Parsing $path\"\n        $AST = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$astTokens, [ref]$astErr)\n\n        #group tokens and turn into a hashtable\n        $h = $astTokens | Group-Object tokenflags -AsHashTable -AsString\n     \n        $commandData = $h.CommandName | where-object { $_.text -notmatch \"-TargetResource$\" } | \n        ForEach-Object {\n            Write-Verbose \"Processing $($_.text)\" \n            Try {\n                $cmd = $_.Text\n                $resolved = $cmd | get-command -ErrorAction Stop\n                if ($resolved.CommandType -eq 'Alias') {\n                    Write-Verbose \"Resolving an alias\"\n                    #manually handle \"?\" because Get-Command and Get-Alias won't.\n                    Write-Verbose \"Detected the Where-Object alias '?'\"\n                    if ($cmd -eq '?') { \n                        Get-Command Where-Object\n                    }\n                    else {\n                        $resolved.ResolvedCommandName | Get-Command\n                    }\n                }\n                else {\n                    $resolved\n                }\n            } \n            Catch {\n                Write-Verbose \"Command is not recognized\"\n                #create a custom object for unknown commands\n                [PSCustomobject]@{\n                    CommandType = \"Unknown\"\n                    Name        = $cmd\n                    ModuleName  = \"Unknown\"\n                }\n            }\n        } \n\n        write-output $CommandData\n    }\n\n    End {\n        Write-Verbose -Message \"Ending $($MyInvocation.Mycommand)\"\n    }\n}\n<\/pre>\n<p>\u00a0<\/p>\n<p>This function was originally from <a href=\"https:\/\/www.petri.com\/powershell-problem-solver-find-script-commands\">Jeff Hicks .<\/a>\u00a0 I made a few tweaks, but the majority of this is his code.\u00a0 As always, Jeff puts out amazing work.<\/p>\n<p>Next, a simple script to call the function on all of the scripts in a certain directory:<\/p>\n\n\n\n<pre class=\"lang:ps decode:true\">if ($IsWindows -or $IsWindows -eq $null){$ModulesDir = (Get-ItemProperty HKLM:\\SOFTWARE\\PWSH).PowerShell_Modules}\nelse{get-content '\/var\/opt\/tifa\/settings.json'}\n$log = $PSScriptRoot + '\\'+ ($MyInvocation.MyCommand.Name).split('.')[0] + '.log'\n$ModulesToImport = 'logging', 'SQL', 'PoshKeePass', 'PoshRSJob', 'SCOM', \"OMI\", 'DynamicMonitoring', 'Nix','Utils'\nforeach ($module in $ModulesToImport) {Get-ChildItem $ModulesDir\\$module\\*.psd1 -Recurse | resolve-path | ForEach-Object { import-module $_.providerpath -force }}\n\n$files = (Get-ChildItem 'D:\\pwsh\\' -Include *.ps1 -Recurse | where-object { $_.FullName -notmatch \"\\\\modules*\" }).fullname\n\n[System.Collections.ArrayList]$AllData = @()\nforeach ($file in $files) {\n    [array]$data = Test-ScriptFile -Path $file\n    foreach ($dataline in $data) {\n        $path = $Dataline.module.path\n        $version = $dataline.Module.version\n        $dataobj = [PSCustomObject]@{\n            File          = $File;\n            Type          = $dataline.CommandType;\n            Name          = $dataline.Name;\n            ModuleName    = $dataline.ModuleName;\n            ModulePath    = $path;\n            ModuleVersion = $version;\n        }\n        $AllData.add($dataobj)|out-null\n    }\n}\n$AllData|select-object -Property file, type, name, modulename,modulepath,moduleversion -Unique|out-gridview\n<\/pre>\n<p>There are a few gotchas you want to keep an eye out for &#8211; in order to correctly identify the cmdlets, you will need to load the module that contains said cmdlet.\u00a0 You can see where I am loading several modules\u00a0 &#8211; SCOM, SQL, logging, etc.\u00a0 The modules in my script are loaded from modules stored in a reg key &#8211; HKLM:\\Software\\PWSH.\u00a0 Load your modules in any way that works for you.\u00a0 Also note that the last line sends the output to a gridview &#8211; change that output to something more useful unless you just previewing it.<\/p>\n<p>And there you have it!\u00a0 Thanks to Jeff for the core function, and I hope this helps keep your modules organized!<\/p>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[16],"class_list":["post-373","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-powershell"],"_links":{"self":[{"href":"https:\/\/draith.com\/index.php?rest_route=\/wp\/v2\/posts\/373","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/draith.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/draith.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/draith.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/draith.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=373"}],"version-history":[{"count":0,"href":"https:\/\/draith.com\/index.php?rest_route=\/wp\/v2\/posts\/373\/revisions"}],"wp:attachment":[{"href":"https:\/\/draith.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=373"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/draith.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=373"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/draith.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=373"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}