tech.guitarrapc.cóm

Technical updates

PowerShellでIISのログから接続元IP一覧を取得する

Windows Server 2012いいですね! もう2008R2以前に戻れません。 さて、IISですが、相変わらず読みにくいログです。

今回、接続元のIPをログから取得、調査するする必要に迫られたので、スクリプトを書きました。 せっかくなので公開しておきます。

IISのログ場所

デフォルトのログ保存パスは以下です。

C:\inetpub\logs\LogFiles\W3SVC1\

次のファイル名規則でログが保持されています。

u_exYYMMdd.log

Git公開

コードはGitで公開しています。 最新は此処から取得してください。

guitarrapc/PowerShellUtil | GitHub

コード全文

最新はGitを参照してください。

#Requires -Version 3.0

[CmdletBinding(
  SupportsShouldProcess = $false,
  ConfirmImpact = "none",
  DefaultParameterSetName = "ExportAsCsv"
)]
param
(
  [Parameter(
  HelpMessage = "Input Path of IIS Log file. Default : C:\inetpub\logs\LogFiles\W3SVC1\",
  Position = 0,
  Mandatory = $false,
  ValueFromPipeline = $true,
  ValueFromPipelineByPropertyName = $true
  )]
  [ValidateNotNullOrEmpty()]
  [string]
  $IISLogPath = "C:\inetpub\logs\LogFiles\W3SVC1\",

  [Parameter(
  HelpMessage = "Input Suffix of IIS Log file. Default : u_ex",
  Position = 1,
  Mandatory = $false,
  ValueFromPipeline = $true,
  ValueFromPipelineByPropertyName = $true
  )]
  [ValidateNotNullOrEmpty()]
  [string]
  $IISLogFileHead =  "u_ex",

  [Parameter(
  HelpMessage = "Input date of IIS Log file . Default : `"`"",
  Position = 2,
  Mandatory = $false,
  ValueFromPipeline = $true,
  ValueFromPipelineByPropertyName = $true
  )]
  [string]
  $IISLogFileDate =  "",

  [Parameter(
  HelpMessage = "Input Extention of IIS Log file. Default : .log",
  Position = 3,
  Mandatory = $false,
  ValueFromPipeline = $true,
  ValueFromPipelineByPropertyName = $true
  )]
  [string]
  $IISLogFileExtention =  ".log",

  [Parameter(
  HelpMessage = "Select switch if you want to output with sort all AddressList to Unique. Default : Not Selected",
  Mandatory = $false,
  ValueFromPipeline = $true,
  ValueFromPipelineByPropertyName = $true
  )]
  [switch]
  $sortUniq,

  [Parameter(
  HelpMessage = "If you select this switch, output with csv. Default selected",
  Mandatory = $false,
  ParameterSetName="ExportAsCsv"
  )]
  [switch]
  $ExportAsCsv,

  [Parameter(
  HelpMessage = "If you select this switch, output with json. Default not selected",
  Mandatory = $false,
  ParameterSetName="ExportAsJson"
  )]
  [switch]
  $ExportAsJson
)



function Get-IisLogFileCIps{

  [CmdletBinding(
    SupportsShouldProcess = $false,
    ConfirmImpact = "none",
    DefaultParameterSetName = "ExportAsCsv"
  )]
  param
  (
    [Parameter(
    HelpMessage = "Input Path of IIS Log file. Default : C:\inetpub\logs\LogFiles\W3SVC1\",
    Position = 0,
    Mandatory = $false,
    ValueFromPipeline = $true,
    ValueFromPipelineByPropertyName = $true
    )]
    [ValidateNotNullOrEmpty()]
    [string]
    $IISLogPath = "C:\inetpub\logs\LogFiles\W3SVC1\",

    [Parameter(
    HelpMessage = "Input Suffix of IIS Log file. Default : u_ex",
    Position = 1,
    Mandatory = $false,
    ValueFromPipeline = $true,
    ValueFromPipelineByPropertyName = $true
    )]
    [ValidateNotNullOrEmpty()]
    [string]
    $IISLogFileHead =  "u_ex",

    [Parameter(
    HelpMessage = "Input date of IIS Log file . Sample : `'2013/04/17`'",
    Position = 2,
    Mandatory = $false,
    ValueFromPipeline = $true,
    ValueFromPipelineByPropertyName = $true
    )]
    [string]
    $IISLogFileDate = "",

    [Parameter(
    HelpMessage = "Input Extention of IIS Log file. Default : .log",
    Position = 3,
    Mandatory = $false,
    ValueFromPipeline = $true,
    ValueFromPipelineByPropertyName = $true
    )]
    [string]
    $IISLogFileExtention =  ".log",

    [Parameter(
    HelpMessage = "Select switch if you want to output with sort all AddressList to Unique. Default : Not Selected",
    Mandatory = $false,
    ValueFromPipeline = $true,
    ValueFromPipelineByPropertyName = $true
    )]
    [switch]
    $sortUniq,

    [Parameter(
    HelpMessage = "If you select this switch, output with csv. Default selected",
    Mandatory = $false,
    ParameterSetName="ExportAsCsv"
    )]
    [switch]
    $ExportAsCsv,

    [Parameter(
    HelpMessage = "If you select this switch, output with json. Default not selected",
    Mandatory = $false,
    ParameterSetName="ExportAsJson"
    )]
    [switch]
    $ExportAsJson

  )

  begin
  {
    $prevstatus = $true
  }

  process
  {

    # Check IIS Log file exit or not, when $IISLogFileDate had passed
    if($IISLogFileDate -ne "")
    {
      # Cast and Perse $IISLogFileDate to use for filename
      $IISLogFileDate = ([datetime]$IISLogFileDate).ToString("yyMMdd")

      $IISLogFileName = ($IISLogFileHead + $IISLogFileDate + $IISLogFileExtention)
      $IISLogFullPath = Join-Path $IISLogPath $IISLogFileName

      if(!(Test-Path $IISLogFullPath))
      {
        throw "$IISLogFileDate format was correct. But $IISLogFullPath not found. Please check $ISLogFileDate format."
      }

    }
    else
    {
      # When $IISLogFileDate not ordered, then get all Files in iis log directory.
      $IISLogFullPath = (Get-ChildItem $IISLogPath).FullName
    }

    $result = foreach ($log in $IISLogFullPath){

      [Console]::WriteLine("$log read start.")

      # Read $log file
      $IISLogFileRaw = Get-Content -Path $log

      # Set Header from log file by reading RAW Number 3
      $headers = $IISLogFileRaw[3].Replace("#Fields: ","").Replace("-","").Replace("(","").Replace(")","").split(" ")

      # Import Log file as Object
      $IISLogFileCSV = Import-Csv -Delimiter " " -Header $headers -Path $log

      # Remove #* line for date object
      $IISLogFileCSV = $IISLogFileCSV | where {$_.date -notlike "#*"}

      # Create PSCustomObject
      $IISLogFileCSV | %{
          # Input into variables to prepare making PSCustomObject
          $date=$_.date
          $time=$_.time
          $sip=$_.sip
          $csmethod=$_.csmethod
          $csuristem=$_.csuristem
          $csuriquery=$_.csuriquery
          $sport=$_.sport
          $csusername=$_.csusername
          $cip=$_.cip
          $csUserAgent=$_.csUserAgent
          $csReferer=$_.csReferer
          $scstatus=$_.scstatus
          $scsubstatus=$_.scsubstatus
          $scwin32status=$_.scwin32status
          $timetaken=$_.timetaken


          #region Debug check cip resut
          <#
          [Console]::WriteLine($_.cip)
          #>
          #endregion

          # Check currentIP and previousIP is same or not, then check previous result. If failed, then skip.
          # 1. Check PreviousIP and CurrentIP
          if(($_.cip -ne $prevCip))
          {
            try
            {
              # DNS Name Resolve for IP Address who connected
              [System.Net.Dns]::GetHostByAddress($_.cip)
              $prevStatus=$true
            }
            catch
            {
              # Create Custom Object as blank
              $prevStatus=$false
              [PSCustomObject]@{
                HostName=""
                Aliases=""
                AddressList=$_.cip
              }
            }
            # flag for next line ip check
            $prevCip=$_.cip
          }
          else
          {
            # 2. Check previous result was succeed or not
            if($prevStatus -eq $false)
            {
            }
            else
            {
              [System.Net.Dns]::GetHostByAddress($_.cip)
              $prevStatus=$true
            }
          }


          #region Debug if result
          <#
          [Console]::WriteLine(($_.cip -eq $prevCip) -and ($prevStatus -eq $true))
          [Console]::WriteLine(($_.cip -eq $prevCip))
          [Console]::WriteLine(($prevStatus -eq $true))
          #>
          #endregion


        } | %{
          # Output as PSCustomObejct and append all file parse data into 1 PSObject
          $Output = [PSCustomObject]@{
            HostName=$_.HostName
            Aliases=[string]$_.Aliases
            AddressList=[string]$_.AddressList
            date=$date
            time=$time
            sip=$sip
            csmethod=$csmethod
            csuristem=$csuristem
            csuriquery=$csuriquery
            sport=$sport
            csusername=$csusername
            cip=$cip
            csUserAgent=$csUserAgent
            csReferer=$csReferer
            scstatus=$scstatus
            scsubstatus=$scsubstatus
            scwin32status=$scwin32status
            timetaken=$timetaken
            iisFileName=$log
            }

          # Output currenct PSCustom Object to temp file.
          switch ($true){
            $ExportAsCsv {$Output | Out-File ./Current_Object_Status_csv.log}
            $ExportAsJson {$Output | Out-File ./Current_Object_Status_json.log}
            default {$Output | Out-File ./Current_Object_Status.log}
          }

          # Output to recieve in $result
          $Output
        }
      [Console]::WriteLine("$log read end and go next step..")
      }
    [Console]::WriteLine("All Log files read done. Starting output...")
  }

  end
  {
    switch($true){
    $sortUniq {$result | sort AddressList -Unique}
    default {$result | sort AddressList}
    }
  }

}


"Now Running Scripts, please wait...."


switch ($true)
{
  $ExportAsCsv
  {
    if($true -eq $sortUniq)
    {
      Get-IisLogFileCIps -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -sortUniq -ExportAsCsv | Export-csv ./IIS_c-IP_lists_$((Get-Date).ToString("yyyyMMdd")).csv -NoTypeInformation
    }
    else
    {
      Get-IisLogFileCIps -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -ExportAsCsv | Export-csv ./IIS_c-IP_lists_$((Get-Date).ToString("yyyyMMdd")).csv -NoTypeInformation
    }
  }

  $ExportAsJson
  {
    if($true -eq $sortUniq)
    {
      Get-IisLogFileCIps -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -sortUniq -ExportAsJson | ConvertTo-Json -Compress | Out-File ./IIS_c-IP_lists_$((Get-Date).ToString("yyyyMMdd")).json
    }
    else
    {
      Get-IisLogFileCIps -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -ExportAsJson | ConvertTo-Json -Compress | Out-File ./IIS_c-IP_lists_$((Get-Date).ToString("yyyyMMdd")).json
    }
  }

  default { Get-IisLogFileCIps -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" | Export-csv ./IIS_c-IP_lists_$((Get-Date).ToString("yyyyMMdd")).csv -NoTypeInformation}
}

"End Scripts. Please check ./IIS_c-IP_lists.log"
pause

利用例

フォルダ内の全ログを取得、分析する場合はこれで大丈夫です。 (デフォルトでcsv取得)

# バッチで実行の場合
PowerShell .\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\"

# PowerShell.exeで実行
.\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\"

出力をjsonにする場合は以下のように、-ExportAsJsonをつけます。

# バッチで実行の場合
PowerShell .\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -ExportAsJson

# PowerShell.exeで実行
.\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\"  -ExportAsJson

更に出力結果をユニーク(一意)なもののみにする場合は、-sortUniqを付けます。

# バッチで実行の場合(csvでuniq)
PowerShell .\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -ExportAsCsv -sortUniq

# PowerShell.exeで実行の場合(csvでuniq)
.\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\"  -ExportAsCsv -sortUniq

日付の指定も可能です。調査したい日付がファイル名を、-IISLogFileDateに渡します。 もし何も渡さなければ、IISログフォルダ全てのログを走査します。 datetime型にキャストするので、所謂日付や、Get-Dateで走査した日付をパイプから渡しても大丈夫です。

# バッチで実行の場合(csvでuniq)
PowerShell .\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\" -ExportAsCsv -sortUniq -IISLogFileDate "2013/03/07"

# PowerShell.exeで実行の場合(csvでuniq)
.\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\"  -ExportAsCsv -sortUniq -ExportAsCsv -sortUniq -IISLogFileDate "2013/03/07"

(Get-Date).AddDays("-30") | %{ .\Get-IisLogFileCIps.ps1 -IISLogPath "C:\inetpub\logs\LogFiles\W3SVC1\"  -ExportAsCsv -sortUniq -ExportAsCsv -sortUniq -IISLogFileDate "2013/03/07" }

リクエストがあり、IISのログも全て出力するようにしています。

別コマンドレットでもいいですし、パラメーターで渡しても、Module化しても良さそうです。

欲しい人はリクエストをどうぞ。更新点は、GitHubでどうぞ。