SHIMSOFT-Fundamentals.psm1

function Get-PropertyNames {
    <#
    .SYNOPSIS
    オブジェクト配列からプロパティ名を取得します。
 
    .DESCRIPTION
    オブジェクト配列からプロパティ名を取得します。
    構成要素の異なるオブジェクトの配列から、すべてのプロパティ名を取得します。
 
 
    .EXAMPLE
    Get-PropertyName -Array $Array
 
 
    .EXAMPLE
    Get-PropertyName -Array $Array
 
 
    .EXAMPLE
    $Array | Get-PropertyName
 
    .EXAMPLE
    $Array | ft -Property (Get-PropertyName -Array $Array)
 
 
    .PARAMETER Array
    オブジェクト配列を指定してください。
 
 
    .LINK
 
    .NOTES
 
    .INPUTS
    パイプラインからの入力可能です。
 
    .OUTPUTS
    文字列
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Always multiple results.")]


    param(
        [Parameter(Mandatory=$false,Position=1,ValueFromPipeLine=$True)]
        [Array]$Array
    )

    begin {
        $PropertyNames = @()
    }

    process {

        foreach ($A in $Array) {
            $PropertyNames += ($A | Get-Member -MemberType Properties -ErrorAction SilentlyContinue).Name
        }
    }

    end {
        ($PropertyNames | Sort-Object -Unique)
    }

}

function Get-MSProductLifeCycle {
    <#
    .SYNOPSIS
    マイクロソフト社製品の製品名からライフサイクル情報を確認します。
 
    .DESCRIPTION
    マイクロソフト社製品の製品名からライフサイクル情報を確認します。
    https://app-omaha-prod.azurewebsites.net/api/PublishedListings/Export(endOfSupportYear=0,endOfSupportMonths=0,family=%27%27,group=%27%27)
    インターネットに接続できない環境の場合は利用できません。
 
    処理のため、モジュール実態フォルダ内に以下の3個のデータファイルが保存されます。
    ・Microsoft 社 Web サイトからダウンロードした Excel ファイル
    ・Excel ファイルを CSV 変換した CSV ファイル
    ・CSV ファイルを処理に合わせて成型した XML ファイル
 
 
    .EXAMPLE
    Windows Server 2016 について確認したい場合。
    Get-MSProductLifeCycle -Product "Windows Server 2016"
 
    .EXAMPLE
    Microsoft Product Life Cycle リストを強制的にリフレッシュしたい場合。
    Get-MSProductLifeCycle -Refresh
 
    .EXAMPLE
    Microsoft Product Life Cycle リストをリフレッシュを抑制したい場合。
    Get-MSProductLifeCycle -Product "Windows Server 2016" -NoRefresh
 
    ただし、処理に必要なデータファイルがそろっていない場合はリフレッシュ動作は強制されます。
 
 
    .EXAMPLE
    2022年中にメインストリームサポートが終了する製品を確認したい。
    Get-MSProductLifeCycle -Product * | where {$_.MainstreamDate -ge "2022/01/01" -and $_.MainstreamDate -le "2022/12/31"}
 
    .EXAMPLE
    2022年中に延長サポートが終了する製品を確認したい。
    Get-MSProductLifeCycle -Product * | where {$_.ExtendedEndDateDate -ge "2022/01/01" -and $_.ExtendedEndDate -le "2022/12/31"}
 
    .EXAMPLE
    2022年中にモダンサイクルポリシー製品の提供が終了する製品を確認したい。
    Get-MSProductLifeCycle -Product * | where {$_.RetirementDate -ge "2022/01/01" -and $_.RetirementDate -le "2022/12/31"}
 
 
    .PARAMETER Product
    マイクロソフト製品名を指定します。半角スペースを含む場合は "Windows Server" のように指定します。
    ワイルドカード指定できます。
    "Exchange*2019" や "Windows 10*21H2"
 
    .PARAMETER Refresh
    Life Cycle 情報を再ダウンロードし、キャッシュをリフレッシュします。
    Life Cycle 情報が古い場合(タイムスタンプが今月ではない) 場合はこのオプション指定有無にかかわらず最新データへのリフレッシします。
    データ再生成に時間がかかります。(ver 1.7 以前と比べ処理時間は高速化されました)
 
    .PARAMETER NoRefresh
    Life Cycle 情報が古い場合(タイムスタンプが今月ではない) 場合も最新データへのリフレッシュをスキップします。
    インターネット通信できない環境や、データリフレッシュにかかる時間を節約したい場合に利用します。
    ただし、処理に必要なデータファイルがそろっていない場合はリフレッシュ動作は強制されます。
 
 
    .LINK
 
    .NOTES
    https://app-omaha-prod.azurewebsites.net/api/PublishedListings/Export(endOfSupportYear=0,endOfSupportMonths=0,family=%27%27,group=%27%27) の Web サイト利用は無許可利用です。
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    ダウンロードした CSV データから生成されたオブジェクトです。
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification="Progress information")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "", Justification="Parameter used.")]

    Param(
        [Parameter(Mandatory=$false,Position=1)]
        [String]$Product="no-product",

        [Parameter(Mandatory=$false,Position=2)]
        [Switch]$Refresh,

        [Parameter(Mandatory=$false,Position=3)]
        [Switch]$NoRefresh
    )

    $PLCExcel = "\ProductLifeCycle_SourceData.xlsx"
    $PLCCSV = "\ProductLifeCycle.csv"
    $PLCCache = "\ProductLifeCycle.xml"

    if ($True -eq $Refresh) {
        Write-Host ("go to Refresh Cache")
        $CacheActive = $False
    } else {
        $CacheActive = $true
    }

    if ($False -eq (Test-Path -Path ("{0}{1}" -f $PSScriptRoot,$PLCExcel)) -or $False -eq (Test-Path -Path ("{0}{1}" -f $PSScriptRoot,$PLCCache)) -or $False -eq (Test-Path -Path ("{0}{1}" -f $PSScriptRoot,$PLCCSV))) {
        $CacheActive = $False
    } else {
        $File = Get-ChildItem -Path ("{0}{1}" -f $PSScriptRoot,$PLCExcel)
        if ((Get-Date -Date $file.LastWriteTime -Format "yyyyMM") -ne (Get-Date -Format "yyyyMM")) {
            if ($False -eq $NoRefresh) {
                $CacheActive = $False
            } else {
                Write-Host ("Cache file is old but No Refresh.")
            }
        }
    }

    if ($False -eq $CacheActive) {
    # Refresh Cache

        Write-Host ("Downloading Microsoft Prodcut Life Cycle List.")

Write-Verbose ("Downloading... {0}" -f (get-date -format "hh:mm:ss"))

        $WebCli = New-Object System.Net.WebClient
        $url ="https://app-omaha-prod.azurewebsites.net/api/PublishedListings/Export(endOfSupportYear=0,endOfSupportMonths=0,family=%27%27,group=%27%27)"
        $WebCli.DownloadFile($url, ("{0}{1}" -f $PSScriptRoot,$PLCExcel))

Write-Verbose ("Download Complete. {0}" -f (get-date -format "hh:mm:ss"))

        Write-Host ("Generate Cache File...")

Write-Verbose ("Open Excel file. {0}" -f (get-date -format "hh:mm:ss"))

        $File = Get-ChildItem -Path ("{0}{1}" -f $PSScriptRoot,$PLCExcel)
        $Excel =New-Object -ComObject Excel.Application
        $Excel.DisplayAlerts=$false
        $book = $Excel.Workbooks.Open($File.FullName)
# $Sheet = $Excel.Worksheets.Item(1)

        $work = $book.SaveAs(("{0}{1}" -f $PSScriptRoot,$PLCCSV),6)

        [void]$book.Close($false)
        [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($book)
Write-Verbose (" Closed book. {0}" -f (get-date -format "hh:mm:ss"))
        [void]$Excel.Quit()
        [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($Excel)
Write-Verbose (" Closed Excel {0}" -f (get-date -format "hh:mm:ss"))

        if ($False -eq $Work) {
            Write-Error ("Can not SaveAs to CSV")
            break
        }
Write-Verbose ("SaveAs CSV file. {0}" -f (get-date -format "hh:mm:ss"))



        $PLCwork = Get-Content -Path ("{0}{1}" -f $PSScriptRoot,$PLCCSV) -Encoding Default
        $PLCtitle = $PLCwork | Where-Object {$_ -like "Product,*"}
        $PLC = $PLCwork[($PLCwork.IndexOf($PLCtitle))..($PLCwork.Count-1)] | ConvertFrom-Csv


Write-Verbose ("Column Title fetching... {0}" -f (get-date -format "hh:mm:ss"))

        $PropList = Get-PropertyNames -Array $PLC

Write-Verbose ("Column Title fetch completed. {0}" -f (get-date -format "hh:mm:ss"))

        $ProductLifeCycle = @()

Write-Verbose ("Records fetching... {0}" -f (get-date -format "hh:mm:ss"))

        foreach ($P in $PLC) {
Write-Verbose (" Row Start. {0}" -f (get-date -format "hh:mm:ss"))
            $Mem = New-Object PSObject
            foreach ($Prop in $PropList) {
                if ($Prop -like "*Date" -and ("" -ne $P.($Prop)))  {
                    $Mem | Add-Member -MemberType NoteProperty -Name $Prop -Value (Get-Date -Date $P.($Prop) -Format "yyyy/MM/dd")
                } else {
                    $Mem | Add-Member -MemberType NoteProperty -Name $Prop -Value $P.($Prop)
                }
            }

            $Mem | Add-Member -MemberType NoteProperty -Name ProductString -Value (@($Mem.Product,$Mem.Edition,$Mem.Release) -join " ")
            $ProductLifeCycle += $Mem
Write-Verbose (" Row End. {0}" -f (get-date -format "hh:mm:ss"))

        }

Write-Verbose ("Records fetch Completed and Close Excel. {0}" -f (get-date -format "hh:mm:ss"))


        $ProductLifeCycle | Export-Clixml -Path ("{0}{1}" -f $PSScriptRoot,$PLCCache)

Write-Verbose ("Exported Cache file. {0}" -f (get-date -format "hh:mm:ss"))

        Write-Host ("Cache file created.")

    } else {
        Write-Verbose ("Using current cache file.")
    }

    $ProductLifeCycle = Import-Clixml -Path ("{0}{1}" -f $PSScriptRoot,$PLCCache)

    $Results = $ProductLifeCycle | Where-Object {$_.ProductString -like ("{0}*" -f $Product)}

        $defaultProperties = @("ProductString","MainstreamDate","ExtendedEndDate","RetirementDate")
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet("DefaultDisplayPropertySet",[string[]]$defaultProperties)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $Results | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers

    $Results
}

function ConvertTo-Narrow {
    <#
    .SYNOPSIS
    全角文字列を半角文字列に変換します。
 
 
    .DESCRIPTION
    全角文字列を半角文字列に変換します。
    すべての全角文字が半角に出来るわけではありません。
    平仮名、片仮名、数字、アルファベットが変換できます。
    全角平仮名は半角片仮名になります。
 
    .EXAMPLE
    ConvertTo-Narrow -String "あいうえお"
 
    .EXAMPLE
    ConvertTo-Narrow -String "12345"
 
    .PARAMETER String
    全角文字列を指定します。
 
    .LINK
 
    .NOTES
 
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    半角に変換した文字列を返します。
 
    #>

    Param(
    [Parameter(Mandatory=$true,Position=1)]
    [String]$String
    )

    $a = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをんぁぃぅぇぉゃゅょっ0123456789"
    $a += "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォャュョッ"
    $a += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    $a += "!#$%&()=~|-ー¥@{}「」[]【】〔〕`”`’`‘/*-+、,。.<>゛ ゜"

    $b = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォャュョッ0123456789"
    $b += "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォャュョッ"
    $b += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    $b += "!#`$%&()=~|-ー\@`{}[][][][]`"`'``/*-+,,..<>゙ ゚"

    $c = "ヴがぎぐげござじずぜぞだぢづでどばびぶべぼぱぴぷぺぽ"
    $c += "ヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポ"

    $d = "ヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポ"
    $d += "ヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポ"


    $Work = $String -Split ""
    $Work = $Work[1..($Work.Count-2)]

    $Result = ""

    foreach ($s in $Work) {
        if ($a.IndexOf($s) -ne -1) {
            # 普通の文字
            $Result += $b[($a.IndexOf($s))]
        } else {
            if ($c.IndexOf($s) -ne -1) {
                # 濁音・半濁音
                $Result += $d.Substring(($c.IndexOf($s)*2),2)
            } else {
                # 変換できない
                $Result += $s
            }
        }
    }

    $Result.Replace(" "," ")
}


function ConvertTo-Wide {
    <#
    .SYNOPSIS
    半角文字列を全角文字列に変換します。
 
 
    .DESCRIPTION
    半角文字列を全角文字列に変換します。
    すべての半角文字が全角に出来るわけではありません。
    片仮名、数字、アルファベットが変換できます。
    半角片仮名は全角片仮名になります。
 
 
    .EXAMPLE
    ConverTo-Wide -String "アイウエオ"
 
    .EXAMPLE
    ConvertTo-Wide -String "12345"
 
    .PARAMETER String
    全角文字列を指定します。
 
    .LINK
 
    .NOTES
 
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    半角に変換した文字列を返します。
 
    #>

    Param(
    [Parameter(Mandatory=$true,Position=1)]
    [String]$String
    )


    $a = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォャュョッ0123456789"
    $a += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    $a += "!#$%&()=~|-ー¥@{}「」[]【】〔〕`”`’`‘/*-+、,。.<>゛ ゜"


    $b = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲンァィゥェォャュョッ0123456789"
    $b += "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    $b += "!#`$%&()=~|-ー\@`{}[][][][]`"`'``/*-+,,..<>゙ ゚"


    $c = "ヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポ"

    $d = "ヴガギグゲゴザジズゼゾダヂヅデドバビブベボパピプペポ"


    $Work = $String -Split ""
    $Work = $Work[1..($Work.Count-2)]

    $Result = ""
    $PreFetch = " "

    foreach ($s in $Work) {
        if ("゙ ゚".IndexOf($s) -ne -1 -and $d.IndexOf($PreFetch+$s) -ne -1) {
            $s = $PreFetch + $s
            $Result = $Result.Substring(0,$Result.Length-1)
        }

        if ($b.IndexOf($s) -ne -1) {
            # 普通の文字
            $Result += $a[($b.IndexOf($s))]
        } else {
            if ($d.IndexOf($s) -ne -1) {
                # 濁音・半濁音
                $Result += $c.Substring(($d.IndexOf($s)/2),1)
            } else {
                # 変換できない
                $Result += $s
            }
        }

        $PreFetch = $s
    }

    $Result.Replace(" "," ")
}

function Test-IsNumeric {
    <#
    .SYNOPSIS
    指定した文字列が数値文字列か検証します。
    数値文字列なら $True を、そうでないなら $False を返します。
    数値文字列とは [int] などにキャスト変換できる文字列を指します。
 
 
    .DESCRIPTION
    指定した文字列が数値文字列か検証します。
    数値文字列なら $True を、そうでないなら $False を返します。
 
 
    .EXAMPLE
    Test-IsNumeric -String "100"
    結果は $True
 
    .EXAMPLE
    Test-IsNumeric -String "123.4"
    結果は $True
 
    .EXAMPLE
    Test-IsNumeric -String "1,234"
    結果は $True
 
    .EXAMPLE
    Test-IsNumeric -String "123"
    結果は $False
    全角文字列は数値文字列とはみなされません。
 
    .EXAMPLE
    Test-IsNumeric -String "123abc"
    結果は $False
 
    .EXAMPLE
    Test-IsNumeric -String "百十五"
    結果は $False
 
    .PARAMETER String
    文字列を指定します。
 
    .LINK
 
    .NOTES
    s
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    半角に変換した文字列を返します。
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification="The variable Work is suprress output int value.")]
    Param(
    [Parameter(Mandatory=$true,Position=1)]
    [String]$String
    )

    Try { $Work = [int]$String; $True } catch { $False }

}

function Format-WindowsFeature {
    <#
    .SYNOPSIS
    Get-WindowsFeature 実行したときのような結果を表示します。
 
    .DESCRIPTION
    Get-WindowsFeature コマンドを実行した結果を保存しておきたいときに、
    Get-WindowsFeature | Export-Clixml -Path WindowsFeature.xml
    として情報を保存したとします。
 
    保存したファイルなどを別の環境で確認したい場合に、以下のようにしたとします。
 
    $WF = Import-Clixml -Path WindowsFeature.xml
    $FW
 
    すると Get-WindowsFeature を実行したときのような結果が出ずにがっかりするんです。
    そこで、 Format-WindowsFeature でもう一度あの表示を得ることができます。
 
 
 
    .EXAMPLE
    Format-WindowsFeature -WindowsFeature (Import-Clixml -Path WindowsFeature.xml)
 
 
    .PARAMETER WindowsFeatures
    Get-WindowsFeatures の結果を指定します。
 
    .LINK
 
    .NOTES
    s
 
    .INPUTS
    なし。パイプラインからの入力は受け付けません。
 
    .OUTPUTS
    半角に変換した文字列を返します。
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Justification="The variable Work is suprress output int value.")]
    Param(
    [Parameter(Mandatory=$true,Position=1)]
    [Array]$WindowsFeature
    )

    $WF_Object = @(
    "AdditionalInfo",
    "BestPracticesModelId",
    "DependsOn",
    "Depth",
    "Description",
    "DisplayName",
    "EventQuery",
    "FeatureType",
    "Installed",
    "InstallState",
    "Name",
    "Notification",
    "Parent",
    "Path",
    "PostConfigurationNeeded",
    "ServerComponentDescriptor",
    "SubFeatures",
    "SystemService")

    if ($WindowsFeature.GetType().BaseType.Name -ne "Array") {
        Write-Error "指定されたデータは Get-WindowsFeature の結果セットではありません。"
        break
    }

    $WF_Check = Get-PropertyNames -Array $WindowsFeature

    if ($WF_Check.Count -ne $WF_Object.Count) {
        Write-Error "指定されたデータは Get-WindowsFeature の結果セットではありません。"
    }

    $isFail = $false
    foreach ($CK in $WF_Check) {
        if ($WF_Object.IndexOf($CK) -eq -1) {
            Write-Error ("指定されたデータに不明なプロパティ値 {0} があります。" -f $CK)
            $isFail = $true
        }
    }
    if ($isFail) { break }

    foreach ($WF in $WindowsFeature) {
        Write-Output ("{0}[{1}] {2}" -f (" "*($WF.Depth-1)),(("X"* $WF.Installed) + (" " * !$WF.Installed)),$WF.DisplayName)
    }
}

Function Compare-ObjectProperties {

    <#
    .SYNOPSIS
    オブジェクトの一致性を確認します。
 
    .DESCRIPTION
    オブジェクトの一致性を確認します。
    戻り値として不一致のプロパティ一覧を返します。
    戻り値が Null の場合、比較対象のオブジェクトは一致している可能性が濃厚です。
 
    一致する項目は返されません。
 
    戻り値に含まれる項目は以下の通りです。
 
    PropertyName : 比較対象オブジェクトに含まれるプロパティの名前です。
    RefValue : 参照側の値
    RefExist : 参照側にプロパティが存在していたかどうか
    RefIsNull : 参照側が Null だったかどうか
    DiffValue : 比較側の値
    DiffExist : 比較側にプロパティが存在していたかどうか
    DiffIsNull : 比較側が Null だったかどうか
    DiffType : 差異種別
 
    オプションフィールド
    SubDiff : Deep 比較動作時に比較結果情報が含まれる。
    DiffArray : プロパティタイプが ArrayList の時に比較参考値が含まれる。
 
    DiffType の種類
    <> : 両方に値が含まれ、相違している
    <=> : 両方に値が含まれ、相違している。そのプロパティ値以下の属性は一致していた。
    <= : 参照側にのみ値が含まれている
    => : 比較側にのみ値が含まれている
    =//= : プロパティが ArrayList であり、アイテム数が同値であり、判定できない。
 
    .EXAMPLE
    Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj
 
    .EXAMPLE
    $Diff = Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj
    if ($null -eq $Diff) { "一致" } else { "相違" }
 
    .EXAMPLE
    比較除外したいプロパティ名がる場合、IgnoteProperties オプションを指定します。
    Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj -IgnoreProperties LastAccessTime,LastAccessTimeUtc
 
    あるオブジェクトで最終アクセス時間が異なることが予想されるが無視したい場合、IgnoreProperties オプションに指定すると比較除外されます。
    無視したいプロパティ名が多数ある場合、配列変数に入れて渡すことが可能です。
 
    $IgnoreProperties = @("LastAccessTime","LastAccessTimeUtc")
    Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj -IgnoreProperties $IgnoreProperties
 
 
    .EXAMPLE
    通常の比較で確認したい差異情報に辿り着かない場合、Deep オプションや Depth オプションを試みることができます。
    オブジェクトのプロパティが階層的に情報を持つ場合、規定値で Depth 3 階層まで掘り下げて確認されます。
    Depth 指定値を大きくすることでより深い階層の値を確認できる可能性があります。
    処理時間がより多くかかります。
 
    Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj -Depth 4
 
    通常比較で一致とされている部分でも Depp モードで比較することにより差異を見つけられる可能性があります。
    処理時間がより多くかかります。
 
    Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj -Deep:$true
 
 
    .EXAMPLE
    プロパティのタイプが ArrayList の場合で、要素数が一致している場合同値と判断され (DiffType = =//=) た場合、
    本当に一致していて確認不要の場合が多い見込みなので結果リストには含まれない様省略されます。
    しかしながら、状況によりこのステータスの情報を確認したい場合があります。
    その場合、IgnoreArrayNoDiffs オプションを $false に指定します。
 
    Compare-ObjectProperties -ReferenceObject $RefObj -DifferenceObject $DiffObj -IgnoreArrayNoDiffs:$false
 
    .EXAMPLE
    Exchange Online 上で User1 と User2 メールボックスの設定値の差異を確認したい場合
    あらかじめ Exchange Online に接続してあることを前提とした例を示します。
 
    $RefMailbox = Get-Mailbox -Identity User1
    $DiffMailbox = Get-Mailbox -Identity User2
 
    Compare-ObjectProperties -ReferenceObject $RefMailbox -DifferenceObject $DiffMailbox
 
 
    .PARAMETER ReferenceObject
    参照側オブジェクトを指定します。
 
    .PARAMETER DifferenceObject
    比較側オブジェクトを指定します。
 
    .PARAMETER Deep
    Deep 比較モードを利用する場合、$True を指定します。規定値 $False です。
 
    .PARAMETER IgnoreProperties
    差異を気にしない無視してよいプロパティ名リストを指定します。
 
    .PARAMETER Depth
    階層的なプロパティ値を指定階層分掘り下げて比較します。規定値は 3 です。
 
    .PARAMETER Prefix
    内部的に利用します。階層的なプロパティを掘り下げ比較する際の親プロパティ名を保持します。
 
    .PARAMETER IgnoreArrayNoDiffs
    プロパティのタイプが ArrayList のとき、参照側・比較側で要素数が一致する場合、おそらく同値 (DiffType = =//=) の場合に
    結果リストに含めないように省略します。
    これらを省略せず結果リストに含めたい場合 $False を指定します。
    規定値 $True です。
 
 
    .LINK
 
    .NOTES
 
    .INPUTS
    パイプラインからの入力出来ません。
 
    .OUTPUTS
    文字列
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Always multiple results.")]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueSwitchParameter", "", Justification="When specify False if nessesary.")]
        [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseProcessBlockForPipelineCommand", "", Justification="Not supported Pipeline Input")]

        param(
                [Parameter(Mandatory=$true,Position=1,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                $ReferenceObject,

                [Parameter(Mandatory=$true,Position=2,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                $DifferenceObject,

                [Parameter(Mandatory=$false,Position=3,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                [Boolean]$Deep=$false,

                [Parameter(Mandatory=$false,Position=4,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                [Array]$IgnoreProperties,

                [Parameter(Mandatory=$false,Position=5,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                [Int]$Depth=3,

                [Parameter(Mandatory=$false,Position=6,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                [String]$Prefix,

                [Parameter(Mandatory=$false,Position=7,ValueFromPipeLine=$False,ValueFromPipelineByPropertyName=$False)]
                [Switch]$IgnoreArrayNoDiffs=$True


        )

    if (1 -eq $IgnoreProperties.Count) { $IgnoreProperties = @($IgnoreProperties[0].Split(","))}

        $RefPropName = Get-PropertyNames -Array $ReferenceObject
    $DiffPropName = Get-PropertyNames -Array $DifferenceObject

    $ComboPropName = ($RefPropName + $DiffPropName) | Sort-Object -Unique

    $DIFFS = @()

    foreach ($Prop in $ComboPropName) {

Write-Verbose ("PropName : {0}.{1} // Depth : {2}" -f $Prefix,$Prop, $Depth)
Write-Verbose (" {0} ;; {1}" -f $ReferenceObject.($Prop), $DifferenceObject.($Prop))

        if ($Prop -notin $IgnoreProperties) {

            $isRefNull = $false
            $isDiffNull = $false

            if ($null -eq $ReferenceObject.($Prop)) {
                if ($null -eq $DifferenceObject.($Prop)) {

Write-Verbose (" Equal both NULL")
                    continue;

                } else {
Write-Verbose (" Diff Ref NULL")
                    $isRefNull = $true
                    $DiffType ="=>"
                    $Mem = New-Object PSObject
                    if ("" -eq $Prefix) {
                        $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value $Prop
                    } else {
                        $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value ("{0}.{1}" -f $Prefix,$Prop)
                    }
                    $Mem | Add-Member -MemberType NoteProperty -Name RefValue -Value $ReferenceObject.($Prop)
                    $Mem | Add-Member -MemberType NoteProperty -Name RefExist -Value $true
                    if (-1 -eq $RefPropName.IndexOf($Prop)) {
    Write-Verbose (" Not have Reference")
                        $Mem.RefExist = $false
                        $DiffType ="=>"
                    }
                    $Mem | Add-Member -MemberType NoteProperty -Name RefIsNull -Value $false
                    if ($null -eq $RefPropName.IndexOf($Prop)) {
                        $Mem.RefIsNull = $true
                    }

                    $Mem | Add-Member -MemberType NoteProperty -Name DiffValue -Value $DifferenceObject.($Prop)
                    $Mem | Add-Member -MemberType NoteProperty -Name DiffExist -Value $true
                    if (-1 -eq $DiffPropName.IndexOf($Prop)) {
    Write-Verbose (" Not have Difference")
                        $Mem.DiffExist = $false
                        $DiffType ="<="
                    }
                    $Mem | Add-Member -MemberType NoteProperty -Name DiffIsNull -Value $false
                    if ($null -eq $DiffPropName.IndexOf($Prop)) {
                        $Mem.DiffIsNull = $true
                    }

                    $Mem | Add-Member -MemberType NoteProperty -Name DiffType -Value $DiffType

                    $DIFFS += $Mem

                    $DiffFlag = $false

                }
            }

            if ($null -ne $ReferenceObject.($Prop) -and $null -eq $DifferenceObject.($Prop)) {
Write-Verbose (" Diff Diff NULL")
                    $isDiffNull = $true
                    $DiffType ="<="
                    $Mem = New-Object PSObject
                    if ("" -eq $Prefix) {
                        $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value $Prop
                    } else {
                        $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value ("{0}.{1}" -f $Prefix,$Prop)
                    }
                    $Mem | Add-Member -MemberType NoteProperty -Name RefValue -Value $ReferenceObject.($Prop)
                    $Mem | Add-Member -MemberType NoteProperty -Name RefExist -Value $true
                    if (-1 -eq $RefPropName.IndexOf($Prop)) {
    Write-Verbose (" Not have Reference")
                        $Mem.RefExist = $false
                        $DiffType ="=>"
                    }
                    $Mem | Add-Member -MemberType NoteProperty -Name RefIsNull -Value $false
                    if ($null -eq $RefPropName.IndexOf($Prop)) {
                        $Mem.RefIsNull = $true
                    }

                    $Mem | Add-Member -MemberType NoteProperty -Name DiffValue -Value $DifferenceObject.($Prop)
                    $Mem | Add-Member -MemberType NoteProperty -Name DiffExist -Value $true
                    if (-1 -eq $DiffPropName.IndexOf($Prop)) {
    Write-Verbose (" Not have Difference")
                        $Mem.DiffExist = $false
                        $DiffType ="<="
                    }
                    $Mem | Add-Member -MemberType NoteProperty -Name DiffIsNull -Value $false
                    if ($null -eq $DiffPropName.IndexOf($Prop)) {
                        $Mem.DiffIsNull = $true
                    }

                    $Mem | Add-Member -MemberType NoteProperty -Name DiffType -Value $DiffType

                    $DIFFS += $Mem

                    $DiffFlag = $false

            }



            $isString = $false

            if ($false -eq $isRefNull -and $false -eq $isDiffNull) {
                if (($ReferenceObject.($Prop) | Get-Member -MemberType Method  -ErrorAction SilentlyContinue | Where-Object {$_.Name -eq "toString"}).Count -eq 1 -and (($ReferenceObject.($Prop).GetType()).Name -ne "ArrayList")) {
                    $isString = $true
                    $DiffFlag = ($ReferenceObject.($Prop).toString() -eq $DifferenceObject.($Prop).toString())
                } else {
                    $DiffFlag = ($ReferenceObject.($Prop) -eq $DifferenceObject.($Prop))
                }
            }

            if ($false -eq $DiffFlag) {


Write-Verbose (" Diif String({0})" -f $isString)

                $DiffType ="<>"

                $Mem = New-Object PSObject
                if ("" -eq $Prefix) {
                    $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value $Prop
                } else {
                    $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value ("{0}.{1}" -f $Prefix,$Prop)
                }


                switch (($ReferenceObject.($Prop).GetType()).Name) {
                    "ArrayList"    {
                        if (($ReferenceObject.($Prop).Count -eq 0) -and ($DifferenceObject.($Prop).Count -eq 0)) {
                            $DIffType="=="
                        } else {

                            $DiffArray = Compare-Object -ReferenceObject $ReferenceObject.($Prop) -DifferenceObject $DifferenceObject.($Prop)
                            $Mem | Add-Member -MemberType NoteProperty -Name DiffArray -Value $DiffArray

                            if ($ReferenceObject.($Prop).Count -lt $DifferenceObject.($Prop).Count) {
                                $DiffType="=>"
                            } else {
                                if ($ReferenceObject.($Prop).Count -gt $DifferenceObject.($Prop).Count) {
                                    $DiffType="<="
                                } else {
                                    $DiffType="=//="

                                    if ($null -ne $DiffArray) {
                                        $DiffType="<>"
                                    } else {
                                        if ($True -eq $IgnoreArrayNoDiffs) {
                                            $DiffType="=="
                                        }
                                    }
                                }
                            }
                        }

                    }

                    default    {
                        if ((($false -eq $isString) -or ($true -eq $Deep)) -and (($ReferenceObject.($Prop)|Get-Member -MemberType Properties  -ErrorAction SilentlyContinue).Count -ge 1 -and (-1 -ne $RefPropName.IndexOf($Prop)) -and (-1 -ne $DiffPropName.IndexOf($Prop)))) {
                            if ((0 -lt $Depth) -and -($false -eq $isRefNull) -and -($false -eq $isDiffNull)) {
        Write-Verbose (" go Deep")
                                if ($null -eq $Prefix) {
                                    $SubDiff = Compare-ObjectProperties -ReferenceObject $ReferenceObject.($Prop) -DifferenceObject $DifferenceObject.($Prop) -Deep:$Deep -IgnoreProperties $IgnoreProperties -Depth ($Depth-1) -Prefix $Prop -IgnoreArrayNoDiffs:$IgnoreArrayNoDiffs
                                } else {
                                    $SubDiff = Compare-ObjectProperties -ReferenceObject $ReferenceObject.($Prop) -DifferenceObject $DifferenceObject.($Prop) -Deep:$Deep -IgnoreProperties $IgnoreProperties -Depth ($Depth-1) -Prefix ("{0}.{1}" -f $Prefix,$Prop) -IgnoreArrayNoDiffs:$IgnoreArrayNoDiffs
                                }
                            } else {
                                if ($false -eq $isRefNull -and $false -eq $isDiffNull) {
                                    if ($ReferenceObject.($Prop).toString() -ne $DifferenceObject.($Prop).toString()) {
                                        $SubMem = New-Object PSObject
                                        if ($null -eq $Prefix) {
                                            $SubMem | Add-Member -MemberType NoteProperty -Name PropertyName -Value $Prop
                                        } else {
            Write-Debug ("Check PropertyName")
                                            $SubMem | Add-Member -MemberType NoteProperty -Name PropertyName -Value ("{0}.{1}" -f $Prefix,$Prop)
                                        }
                                        $SubMem | Add-Member -MemberType NoteProperty -Name RefValue -Value $ReferenceObject.($Prop)
                                        $SubMem | Add-Member -MemberType NoteProperty -Name RefExist -Value $true
                                        $SubMem | Add-Member -MemberType NoteProperty -Name DiffValue -Value $DifferenceObject.($Prop)
                                        $SubMem | Add-Member -MemberType NoteProperty -Name DiffExist -Value $true
                                        $SubMem | Add-Member -MemberType NoteProperty -Name DiffType -Value "<>"

                                        $SubDiff = @($SubMem)
                                    }
                                }
                            }
                            if (0 -eq $SubDiff.Count) {
        Write-Verbose (" Deep Equal")
                                $DiffType = "<=>"

                                if ($true -eq $isString -and $true -eq $DiffFlag) {
                                    $DiffType = "<>"
        Write-Verbose (" but Diff Reason toString")
                                }

                            } else {
                                $Mem | Add-Member -MemberType NoteProperty -Name SubDiff -Value $SubDiff
                            }
                        }
                    }
                }



                $Mem | Add-Member -MemberType NoteProperty -Name RefValue -Value $ReferenceObject.($Prop)
                $Mem | Add-Member -MemberType NoteProperty -Name RefExist -Value $true
                if (-1 -eq $RefPropName.IndexOf($Prop)) {
Write-Verbose (" Not have Reference")
                    $Mem.RefExist = $false
                    $DiffType ="=>"
                }
                $Mem | Add-Member -MemberType NoteProperty -Name RefIsNull -Value $false
                if ($null -eq $RefPropName.IndexOf($Prop)) {
                    $Mem.RefIsNull = $true
                }

                $Mem | Add-Member -MemberType NoteProperty -Name DiffValue -Value $DifferenceObject.($Prop)
                $Mem | Add-Member -MemberType NoteProperty -Name DiffExist -Value $true
                if (-1 -eq $DiffPropName.IndexOf($Prop)) {
Write-Verbose (" Not have Difference")
                    $Mem.DiffExist = $false
                    $DiffType ="<="
                }
                $Mem | Add-Member -MemberType NoteProperty -Name DiffIsNull -Value $false
                if ($null -eq $DiffPropName.IndexOf($Prop)) {
                    $Mem.DiffIsNull = $true
                }

                $Mem | Add-Member -MemberType NoteProperty -Name DiffType -Value $DiffType

                if ("==" -ne $DiffType) {
                    $DIFFS += $Mem
                }
            } else {
Write-Verbose (" Equal")
                if ($True -eq $Deep -and ($ReferenceObject.($Prop)|Get-Member -MemberType Properties).Count -ge 1) {

                    if (0 -lt $Depth) {
Write-Verbose (" go Deep")
                        if ("" -eq $Prefix) {
                            $SubDiff = Compare-ObjectProperties -ReferenceObject $ReferenceObject.($Prop) -DifferenceObject $DifferenceObject.($Prop) -Deep $Deep -IgnoreProperties $IgnoreProperties -Depth ($Depth-1) -Prefix $Prop -IgnoreArrayNoDiffs:$IgnoreArrayNoDiffs
                        } else {
                            $SubDiff = Compare-ObjectProperties -ReferenceObject $ReferenceObject.($Prop) -DifferenceObject $DifferenceObject.($Prop) -Deep $Deep -IgnoreProperties $IgnoreProperties -Depth ($Depth-1) -Prefix ("{0}.{1}" -f $Prefix,$Prop) -IgnoreArrayNoDiffs:$IgnoreArrayNoDiffs
                        }
                        if (0 -ne $SubDiff.Count) {
Write-Verbose (" Deep Diff")
                            $DiffType ="<>"
                            $Mem = New-Object PSObject
                            $Mem | Add-Member -MemberType NoteProperty -Name PropertyName -Value $Prop
                            $Mem | Add-Member -MemberType NoteProperty -Name SubDiff -Value $SubDiff
                            $Mem | Add-Member -MemberType NoteProperty -Name RefValue -Value $ReferenceObject.($Prop)
                            $Mem | Add-Member -MemberType NoteProperty -Name RefExist -Value $true
                            $Mem | Add-Member -MemberType NoteProperty -Name DiffValue -Value $DifferenceObject.($Prop)
                            $Mem | Add-Member -MemberType NoteProperty -Name DiffExist -Value $true
                            $Mem | Add-Member -MemberType NoteProperty -Name DiffType -Value $DiffType

                            $DIFFS += $Mem
                        }
                    }
                }
            }
        } else {
Write-Verbose (" Ignored")
        }
    }

        $defaultProperties = @("PropertyName","DiffType","RefValue","DiffValue")
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet("DefaultDisplayPropertySet",[string[]]$defaultProperties)
        $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $DIFFS | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers

    $DIFFS
}



Export-ModuleMember -Function Get-PropertyNames, Get-MSProductLifeCycle, ConvertTo-Narrow, ConvertTo-Wide, Test-IsNumeric, Format-WindowsFeature, Compare-ObjectProperties