I have a small function that works as a script including the help I built into it.
Using help .\Script.ps1 works and the help shows just fine.
I wanted to make this a module and publish it to an internal repository. I renamed the .ps1 file to .psm1 and successfully published to our repository.
It installs using install-module just fine, but that is where my successes end… I can’t use the function or call up the help for it.
The module gets installed into a module directory in the path variable. The Get-Module -ListAvailable see’s it is there, I just can’t use it. Also Import-Module doesn’t seem to do anything, no errors are reported.
Here is the code, which works as a .ps1 file. Again, once published into our internal repository and installed locally I can’t seem to access it. I’m not sure what I’m doing wrong…
function Get-ADMissingServers {
[CmdletBinding()]
param (
[Parameter(
Position = 0,
Mandatory = $false
)]
[int]
$LastSeen = -180
)
<#
.SYNOPSIS
Retrieves Windows servers from Active Directory that have not been seen for a period of time.
.DESCRIPTION
Use this to locate servers in Active Directory that appear to be missing. Missing servers are determined using the last logon date stored in AD. A default date
of 180 days is used in the abscense of a specified date.
.NOTES
This function is not supported in Linux and only retrieves Windows based servers from Active Directory.
This function only uses an negative integer number to specify the "LastSeen" days; ie:"-25","-90","-365" etc.
.EXAMPLE
Get-ADMissingServers.ps1
Retrieves all Windows servers that are enabled in Active Directory but have not been seen by AD since the default past date (-180 days from the current date.)
.EXAMPLE
Get-ADMissingServers.ps1 -LastSeen -365
Retrieves all Windows servers that are enabled in Active Directory but have not been seen by AD in the last year.
.EXAMPLE
Get-ADMissingServers.ps1 -LastSeen -90 -Verbose
Retrieves all Windows servers that are enabled in Active Directory but have not been seen by AD in the 90 days with verbose output.
#>
begin {
}
process {
$ServerResults = @()
$MissingServers = @()
Write-Verbose "Retrieving all Servers from Active Directory."
$Servers = Get-ADComputer -Filter 'OperatingSystem -Like "*Server*"' -Properties DNSHostName, Enabled, LastLogonTimeStamp, operatingsystem, DistinguishedName
Write-Verbose "Processing each AD Server object."
$Servers | ForEach-Object -Process {
$ServerData = [PSCustomObject]@{
DNSHostName = 'UNKNOWN'
Enabled = 'UNKNOWN'
LastLogonDate = 'UNKNOWN'
OperatingSystem = 'UNKNOWN'
OU = 'UNKNOWN'
}
$ServerData.DNSHostName = $_.DNSHostName
$ServerData.Enabled = $_.enabled
$ServerData.LastLogonDate = $([datetime]::FromFileTime($_.LastLogonTimeStamp))
$ServerData.OperatingSystem = $_.OperatingSystem
$ServerData.OU = $_.DistinguishedName
$ServerResults += $ServerData
Write-Verbose "Processed $_.DNSHostName."
}
Write-Verbose "Comparing each AD Server object's last login time stamp with today $LastSeen."
$ServerResults | ForEach-Object -Process {
if ($_.LastLogonDate -le (Get-Date).AddDays($LastSeen)) {
#Do something
if ($_.OU.EndsWith("OU=Disabled_Servers,OU=ComputerAccounts,DC=canfor,DC=ca")) {
#do nothing
Write-Verbose "$_.DNSHostName does not fall within the criteria."
}
else {
$MissingServers += $_
Write-Verbose "$_.DNSHostName hasn't been seen since $_.LastLogonDate."
}
}
}
Write-Verbose "Outputing the final results."
Return Write-Output $MissingServers
}
end {
}
}
How did you publish it? And how are you importing it? Historically, if I wanted to make a script a module, I simply rename to psm1 like you did, and then put it in a folder with the same name somewhere in the psmodulepath. I just tested this with your function, and it worked fine.
It’s better to be explicit. One of the reasons is performance (though smaller modules it won’t matter), but the better one, IMO, is to get tab completion to work.
Once that gets annoying most people build it as part of a task in a CI/CD pipeline, but… that’s a whole other thing.
That didn’t make any difference.
Just to be certain I made sure to uninstall-module the ADMissingServers module(s) from the system before installing it again. I also deleted any instance(s) of it from the repository.
I am already using a manifest. I used the New-ModuleManifest cmdlet to create it. It took a couple of tries to get the export cmd formatted correctly before the publish would work, but this works without errors.
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Get-ADMissingServers'
Alright, I copied your function to a file, made it a .psm1, made a new module manifest for it and made my “FunctionsToExport” line look like yours. I skipped publishing/installing it and just made a directory for it at "C:\Program Files\Windowspowershell\modules\ADMissingServers\1.0"
Then I imported it, and got the same results as you. I tried moving the comment based help before the param() block, still no luck. I tried altering the psd1 “FunctionsToExport” to explicitly be an array
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @('Get-ADMissingServers')
And it stilll didn’t work. Running:
Get-Command -Module ADMissingServers
Still returns nothing.
So the good news is, you’re not crazy. I’m going to keep tinkering.
IIRC Publishing a module requires a manifest. When you created your manifest (psd1) did you fill out the rootmodule in the file manually or via New-ModuleFaniest?
Sounds like the RootModule might be default (commented out). so I’d suggest uncommenting it and setting it equal to your PSM1.
#this is excerpt from the PSD1 file
# Script module or binary module file associated with this manifest.
RootModule = 'ADMissingServers.psm1'
That way when you import the module using the PSD1, it knows to dot source your ‘root’ psm1’ file which should load your function into memory. That, along with your FunctionsToexport, should export that function.
IMO when using a manifest file, FunctionsToExport should be used, not Export-ModuleMember. in a PSM1, if you don’t have Export-ModuleMember, and you load a module straight from PSM1, i think it just works.
i think @dotnVo wins. I noticed that RootModule wasn’t filled out in my new .psd1 I made but my other modules do have that. I added it (like he showed above) and when I imported it I was greeted with this verbose text:
VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\ADMissingServers\1.0\ADMissingServers.psd1'.
VERBOSE: Loading module from path 'C:\Program Files\WindowsPowerShell\Modules\ADMissingServers\1.0\ADMissingServers.psm1'.
VERBOSE: Exporting function 'Get-ADMissingServers'.
VERBOSE: Importing function 'Get-ADMissingServers'.