Skaldhor.HelperFunctions.psm1

function Get-DmarcRecord{
    [CmdletBinding()] # for standard parameters like -Verbose or -ErrorAction
    param(
        [Parameter(Mandatory=$true, HelpMessage="Domain you want to get the DMARC record for. For example: gmail.com")] [string]$Domain
    )
    try{
        (Resolve-DnsName -Name "_dmarc.$($Domain)" -Type "TXT" -ErrorAction "Stop").Text
    }catch{
        throw "Error during DNS resolution: $($_.Exception.Message)"
    }
    
}

function Get-ExternalTcpConnection{
    try{
        $Connections = Get-NetTCPConnection -ErrorAction "Stop" | Where-Object{($_.RemoteAddress -ne "::") -and ($_.RemoteAddress -ne "::1") -and ($_.RemoteAddress -ne "127.0.0.1") -and ($_.LocalPort -ne 0) -and ($_.RemotePort -ne 0) -and ($_.OwningProcess -ne 0)}
    }catch{
        throw "Error when listing all TCP connections: $($_.Exception.Message)"
    }
    try{
        foreach($Connection in $Connections){
            $OwningProcessInfo = Get-Process -Id $Connection.OwningProcess -ErrorAction "Stop"
            $Connection | Add-Member -NotePropertyName "OwningProcessName" -NotePropertyValue $OwningProcessInfo.ProcessName -ErrorAction "Stop"
            $Connection | Add-Member -NotePropertyName "OwningProcessInfo" -NotePropertyValue $OwningProcessInfo -ErrorAction "Stop"
            $Connection
        }
    }catch{
        throw "Error during process query: $($_.Exception.Message)"
    }
}

function Get-IpConfig{
    # declare paramters
    param(
        [parameter(Mandatory=$false)] [string]$InterfaceAlias
    )

    # get config depending on the $InterfaceAlias input
    if(($null -ne $InterfaceAlias) -and ($InterfaceAlias -ne "")){
        $Configs = Get-NetIPConfiguration -InterfaceAlias $InterfaceAlias
    }else{
        $Configs = Get-NetIPConfiguration
    }
    $Objects = foreach($Config in $Configs){
        # build custom object
        [ordered]@{
            NetworkName = $Config.NetProfile.Name
            Alias = $Config.InterfaceAlias
            Index = $Config.InterfaceIndex
            IPv4Address = $Config.IPv4Address
            IPv4DnsServer = ($Config.DNSServer | Where-Object{$_.AddressFamily -eq 2}).ServerAddresses
            IPv6Address = $Config.IPv6Address
            IPv6DnsServer = ($Config.DNSServer | Where-Object{$_.AddressFamily -eq 23}).ServerAddresses
        }
    }
    $Objects = $Objects | ForEach-Object{New-Object object | Add-Member -NotePropertyMembers $_ -PassThru}
    $Objects | Format-Table
}

function Get-ModulesWithMultipleVersions{
    $AllModules = Get-InstalledModule
    foreach($Module in $AllModules){
        $Versions = Get-InstalledModule -Name $Module.Name -AllVersions
        if($Versions.Count -ge 2){
            Write-Host $Module.Name
        }
    }
}

function Get-MxRecord{
    [CmdletBinding()] # for standard parameters like -Verbose or -ErrorAction
    param(
        [Parameter(Mandatory=$true, HelpMessage="Domain you want to get the MX record for. For example: gmail.com")] [string]$Domain
    )
    try{
        Resolve-DnsName -Name $Domain -Type "MX" -ErrorAction "Stop" | Sort-Object -Property "Preference"
    }catch{
        throw "Error during DNS resolution: $($_.Exception.Message)"
    }
    
}

function Get-PublicIp{
    $PublicIpApiUrls = @(
        "https://ifconfig.me/ip",
        "https://api.myip.com",
        "https://ipinfo.io/ip",
        "https://icanhazip.com"
    )
    foreach($ApiUrl in $PublicIpApiUrls){
        try{
            $Ip = Invoke-RestMethod -Uri $ApiUrl -ErrorAction "Stop"
            Write-Host "Response from '$($ApiUrl)':"
            if($null -ne $Ip.Ip){
                $Ip.Ip
            }else{
                $Ip
            }
            break
        }catch{
            Write-Host "Can't reach '$($ApiUrl)'. Error: $($_.Exception.Message)"
        }
    }
}

function Get-RegistryItem{
    param(
        [Parameter(Mandatory=$true, HelpMessage="Path in the format 'HKxx:\path\to\registryKey'.")] [string]$Path
    )
    $Key = Get-Item -Path $Path
    $Items = $Key.GetValueNames()
    $ItemList = foreach($Item in $Items){
        [ordered]@{
            Name = $Item
            Type = $Key.GetValueKind($Item)
            Value = $Key.GetValue($Item)
        }
    }
    $ItemObjects = $ItemList | ForEach-Object{New-Object object | Add-Member -NotePropertyMembers $_ -PassThru}
    $ItemObjects
}

function Get-SpfRecordEntryIp{
    [CmdletBinding()] # for standard parameters like -Verbose or -ErrorAction
    param(
        [Parameter(Mandatory=$true, HelpMessage="Get IP address for SPF entry in the format 'type:value'. For example: include:spf.protection.outlook.com")] [string]$SpfEntry
    )

    try{
        $Ip = $null
        if($SpfEntry -like "a:*"){
            $Ip = (Resolve-DnsName -Name $SpfEntry.Split(":")[1] -Type "A_AAAA" -ErrorAction "Stop").IPAddress
        }elseif($SpfEntry -like "include:*") {
            $Domains = Resolve-DnsName -Name $SpfEntry.Split(":")[1] -Type "TXT" -ErrorAction "Stop"
            $SpfEntries = $Domains.Text.Split(" ")[1..($Domains.Text.Split(" ").Count - 2)]
            foreach($SpfEntry in $SpfEntries){
                Get-SpfRecordEntryIp -SpfEntry $SpfEntry
            }
        }elseif($SpfEntry -like "ip4:*"){
            $Ip = $SpfEntry.Substring(4,($SpfEntry.length - 4))
        }elseif($SpfEntry -like "ip6:*"){
            $Ip = $SpfEntry.Substring(4,($SpfEntry.length - 4))
        }elseif($SpfEntry -like "mx*"){
            if($SpfEntry -eq "mx"){
                $MxDomain = $Domain
            }elseif($SpfEntry -like "mx:*"){
                $MxDomain = $SpfEntry.Split(":")[1]
            }else{
                throw "Error: Invalid MX record!"
            }

            $Domains = Resolve-DnsName -Name $MxDomain -Type "MX" -ErrorAction "Stop"
            $SpfEntries = $Domains.NameExchange
            foreach($SpfEntry in $SpfEntries){
                Get-SpfRecordEntryIp -SpfEntry "a:$($SpfEntry)"
            }
        }elseif($SpfEntry -like "redirect=*"){
            $TxtRecords = Resolve-DnsName -Name $SpfEntry.Substring(9,($SpfEntry.length - 9)) -Type "TXT" -ErrorAction "Stop"
            $SpfRecord = $TxtRecords | Where-Object{$_.Text -like "v=spf*"}
            if($SpfRecord.Count -gt 1){
                throw "Error: Domain has more than one SPF record!"
            }

            if($SpfRecord.Text -like "*all"){
                $SpfEntries = $SpfRecord.Text.Split(" ")[1..($SpfRecord.Text.Split(" ").Count - 2)]
            }else{
                $SpfEntries = $SpfRecord.Text.Split(" ")[1..($SpfRecord.Text.Split(" ").Count - 1)]
            }

            foreach($SpfEntry in $SpfEntries){
                Get-SpfRecordEntryIp -SpfEntry $SpfEntry
            }
        }else{
            throw "Error: Invalid SPF Syntax in '$($SpfEntry)'!"
        }
        $Ip
    }catch{
        throw "Error during DNS resolution: $($_.Exception.Message)"
    }
}

function Get-SpfRecord{
    [CmdletBinding()] # for standard parameters like -Verbose or -ErrorAction
    param(
        [Parameter(Mandatory=$true, HelpMessage="Domain you want to get the SPF record for. For example: gmail.com")] [string]$Domain
    )
    
    try{
        $TxtRecords = Resolve-DnsName -Name $Domain -Type "TXT" -ErrorAction "Stop"
        $SpfRecord = $TxtRecords | Where-Object{$_.Text -like "v=spf*"}
        if($SpfRecord.Count -gt 1){
            throw "Error: Domain has more than one SPF record!"
        }

        if($SpfRecord.Text -like "*all"){
            $SpfEntries = $SpfRecord.Text.Split(" ")[1..($SpfRecord.Text.Split(" ").Count - 2)]
        }else{
            $SpfEntries = $SpfRecord.Text.Split(" ")[1..($SpfRecord.Text.Split(" ").Count - 1)]
        }

        $ResolvedIps = @()
        foreach($SpfEntry in $SpfEntries){
            $ResolvedIps += Get-SpfRecordEntryIp -SpfEntry $SpfEntry
        }

        Write-Host "SPF Record String:"
        $SpfRecord.Text

        Write-Host ""
        Write-Host ""

        Write-Host "Direct SPF Record entries:"
        $SpfEntries

        Write-Host ""
        Write-Host ""

        Write-Host "All Resolved IPs:"
        $ResolvedIps | Sort-Object -Unique
    }catch{
        throw "Error during DNS resolution: $($_.Exception.Message)"
    }
}

function New-RegistryItem{
    param(
        [Parameter(Mandatory=$true, HelpMessage="Path in the format 'HKxx:\path\to\new\item.filetype'. Filetype must be one of: 'String', 'DWord', 'QWord', 'Binary', 'MultiString', 'ExpandString', 'Unknown'.")] [string]$Path,
        [Parameter(Mandatory=$true, HelpMessage="Value for the new item.")] $Value
    )

    # split path to substrings
    $ParentPath = Split-Path -Path $Path -Parent
    $ItemNameArray = (Split-Path -Path $Path -Leaf).Split(".")
    $ItemName = $ItemNameArray[0..(($ItemNameArray).length - 2)] -join "."
    $ItemType = (Split-Path -Path $Path -Leaf).Split(".")[-1]

    # create registry key, if it doesn't exist already
    if((Test-Path -Path $ParentPath) -eq $false){
        New-Item -Path $ParentPath -ItemType Directory -Force
    }

    # create registry item, if it doesn't exist already and delete then recreate it if it exists
    if($null -eq (Get-ItemProperty -Path $ParentPath -Name $ItemName -ErrorAction SilentlyContinue)){
        New-ItemProperty -Path $ParentPath -Name $ItemName -Value $Value -PropertyType $ItemType
    }else{
        Remove-ItemProperty -Path $ParentPath -Name $ItemName -Force -Confirm:$false
        New-ItemProperty -Path $ParentPath -Name $ItemName -Value $Value -PropertyType $ItemType
    }
}

function Remove-OldModuleVersions{
    param(
        [parameter(Mandatory=$true)] [array]$ModuleNames
    )
    if($ModuleNames -ne "All"){
        $Modules = foreach($ModuleName in $ModuleNames){
            try{
                Get-InstalledModule -Name $ModuleName -ErrorAction Stop
            }catch{
                Write-Host "Cannot get module '$($ModuleName)', maybe it is not installed. Error:"
                Write-Host $_.Exception.Message
            }
        }
    }else{
        $Modules = Get-InstalledModule
    }
    foreach($Module in $Modules){
        $Versions = Get-InstalledModule -Name $Module.Name -AllVersions
        if($Versions.Count -eq 1){
            Write-Host "Only one version ($($Versions.Version)) of module '$($Module.Name)' is installed."
        }else{
            $OldVersions = $Versions | Sort-Object -Property "Version" -Top ($Versions.Count - 1)
            foreach($OldVersion in $OldVersions){
                try{
                    Uninstall-Module -Name $OldVersion.Name -RequiredVersion $OldVersion.Version -Force -ErrorAction Stop
                    Write-Host "Uninstalled module '$($OldVersion.Name)' version '$($OldVersion.Version)'."
                }catch{
                    Write-Host "Cannot uninstall module '$($OldVersion.Name)' version '$($OldVersion.Version)'. Error:"
                    Write-Host $_.Exception.Message
                }
            }
            $CurrentVersion = $Versions | Sort-Object -Property "Version" -Bottom 1
            Write-Host "Current version of module '$($CurrentVersion.Name)' is '$($CurrentVersion.Version)'."
        }
    }
}

function Remove-RegistryItem{
    param(
        [Parameter(Mandatory=$true, HelpMessage="Path in the format 'HKxx:\path\to\itemToDelete'.")] [string]$Path
    )
    $ParentPath = Split-Path -Path $Path -Parent
    $ItemName = Split-Path -Path $Path -Leaf
    Remove-ItemProperty -Path $ParentPath -Name $ItemName -Force -Confirm:$false
}

function Test-PrivateIp{
    param(
        [string]$IpAddress
    )
    # create function to compare IPs
    function Get-Int32FromIp{
        param(
            [string]$Ip
        )
        $IpAddressBytes = [system.net.ipaddress]::Parse($Ip).GetAddressBytes()
        [array]::Reverse($IpAddressBytes)
        $Int32Ip = [system.BitConverter]::ToUInt32($IpAddressBytes, 0)
        $Int32Ip
    }

    # hardcode private IP networks
    $IpRange1Start = Get-Int32FromIp -Ip "10.0.0.0"
    $IpRange1End = Get-Int32FromIp -Ip "10.255.255.255"
    $IpRange2Start = Get-Int32FromIp -Ip "172.16.0.0"
    $IpRange2End = Get-Int32FromIp -Ip "172.31.255.255"
    $IpRange3Start = Get-Int32FromIp -Ip "192.168.0.0"
    $IpRange3End = Get-Int32FromIp -Ip "192.168.255.255"

    # process input IP
    try{
        $IpAddress = Get-Int32FromIp -Ip $IpAddress -ErrorAction "Stop"
    }catch{
        throw $_.Exception.Message
    }
    $IsPrivateIp = $false
    if(($IpRange1Start -le $IpAddress) -and ($IpAddress -le $IpRange1End)){
        $IsPrivateIp = $true
    }elseif(($IpRange2Start -le $IpAddress) -and ($IpAddress -le $IpRange2End)){
        $IsPrivateIp = $true
    }elseif(($IpRange3Start -le $IpAddress) -and ($IpAddress -le $IpRange3End)){
        $IsPrivateIp = $true
    }

    # return boolean
    $IsPrivateIp
}

Export-ModuleMember -Function Get-DmarcRecord, Get-ExternalTcpConnection, Get-IpConfig, Get-ModulesWithMultipleVersions, Get-MxRecord, Get-PublicIp, Get-RegistryItem, Get-SpfRecordEntryIp, Get-SpfRecord, New-RegistryItem, Remove-OldModuleVersions, Remove-RegistryItem, Test-PrivateIp