DSCResources/cFileAssoc/cFileAssoc.psm1
using namespace Microsoft.Win32 # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [ValidateSet("Present", "Absent")] [string] $Ensure = 'Present', [parameter(Mandatory = $true)] [string] $Extension, [parameter()] [string] $Command, [parameter()] [string] $FileType, [parameter()] [string] $Icon ) $GetRes = @{ Ensure = $Ensure Extension = $Extension Command = '' FileType = '' Icon = '' } $GetAssoc = Get-FileAssoc -Extension $Extension $GetRes.FileType = $GetAssoc.FileType $GetRes.Command = $GetAssoc.Command $GetRes.Icon = $GetAssoc.Icon if ($GetRes.Command -and $GetRes.FileType) { $GetRes.Ensure = 'Present' } else { $GetRes.Ensure = 'Absent' } $GetRes } # end of Get-TargetResource # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [ValidateSet("Present", "Absent")] [string] $Ensure = 'Present', [parameter(Mandatory = $true)] [string] $Extension, [parameter()] [string] $Command, [parameter()] [string] $FileType, [parameter()] [string] $Icon ) $Ret = $true $CurrentState = Get-TargetResource -Ensure $Ensure -Extension $Extension if ($Ensure -ne $CurrentState.Ensure) { # Not match Ensure state Write-Verbose ('Not match Ensure state. your desired "{0}" but current "{1}"' -f $Ensure, $CurrentState.Ensure) $Ret = $Ret -and $false } if ($Ensure -eq 'Present') { if ($PSBoundParameters.Command -and ($Command -ne $CurrentState.Command)) { # Not match associated command Write-Verbose ('Command attr is not match (Current:"{0}" / Desired:"{1}")' -f $CurrentState.Command, $Command) $Ret = $Ret -and $false } if ($PSBoundParameters.FileType -and ($FileType -ne $CurrentState.FileType)) { # Not match FileType (optional) Write-Verbose ('FileType attr is not match (Current:"{0}" / Desired:"{1}")' -f $CurrentState.FileType, $FileType) $Ret = $Ret -and $false } if ($PSBoundParameters.Icon -and ($Icon -ne $CurrentState.Icon)) { # Not match Icon (optional) Write-Verbose ('Icon attr is not match (Current:"{0}" / Desired:"{1}")' -f $CurrentState.Icon, $Icon) $Ret = $Ret -and $false } } return $Ret } # end of Test-TargetResource # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// function Set-TargetResource { [CmdletBinding()] param ( [ValidateSet("Present", "Absent")] [string] $Ensure = 'Present', [parameter(Mandatory = $true)] [string] $Extension, [parameter()] [string] $Command, [parameter()] [string] $FileType, [parameter()] [string] $Icon ) if ($Ensure -eq 'Absent') { #関連付け削除 Write-Verbose ('Your desired state is "Absent". Start trying to remove file association of "{0}"' -f $Extension) Remove-FileAssoc -Extension $Extension } elseif ($Ensure -eq 'Present') { #関連付け登録 Write-Verbose ('Your desired state is "Present". Start trying to associate file type of "{0}"' -f $Extension) $Res = @{ Extension = $Extension } if ($PSBoundParameters.FileType) { #FileType指定あり -> 指定されたFileTypeを使う Write-Verbose ('FileType: {0}' -f $FileType) $Res.FileType = $FileType } elseif ($PSBoundParameters.Command) { #FileType未設定 & FileType指定なし -> 拡張子(ドット無)+file を使う eg).txt -> txtfile Write-Verbose ('Command: {0}' -f $Command) $Res.Command = $Command } # アイコン指定あり if ($PSBoundParameters.Icon) { Write-Verbose ('Icon: {0}' -f $Icon) $Res.Icon = $Icon } Set-FileAssoc @Res } } # end of Set-TargetResource # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// function Get-FileAssoc { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Extension ) $Ret = @{ Extension = $Extension FileType = '' Command = '' Icon = '' } # ユーザ固有の関連付けがある場合はそちらを取得 $UserChoicePath = ("HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{0}\UserChoice" -f $Extension) if ((Test-Path -LiteralPath $UserChoicePath) -and (Get-ItemProperty -LiteralPath $UserChoicePath).ProgId) { $Ret.FileType = (Get-ItemProperty -LiteralPath $UserChoicePath).ProgId } # なければシステム全体の関連付けを取得 else { $GetFileType = & cmd.exe /c ("assoc {0} 2>null" -f $Extension) foreach ($Line in $GetFileType) { if ($Line -match '=') { $Ret.FileType = $Line.Split("=")[1].Trim() } } } if ($Ret.FileType) { $GetCommand = & cmd.exe /c ("ftype {0} 2>null" -f $Ret.FileType) foreach ($Line in $GetCommand) { if ($Line -match '=') { $Ret.Command = $Line.Split("=")[1].Trim() } } $RegKey = [Registry]::LocalMachine.OpenSubKey(("SOFTWARE\Classes\{0}\DefaultIcon" -f $Ret.FileType)) if ($RegKey) { $Ret.Icon = $RegKey.GetValue($null, $null, [RegistryValueOptions]::DoNotExpandEnvironmentNames) $RegKey.Close() } } $Ret } # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// function Set-FileAssoc { [CmdletBinding(DefaultParameterSetName = 'FileType')] Param( [Parameter(Mandatory)] [string] $Extension, [Parameter(Mandatory, ParameterSetName = 'FileType')] [string] $FileType, [Parameter(Mandatory, ParameterSetName = 'Command')] [string] $Command, [Parameter()] [string] $Icon ) # ユーザ固有の関連付けは削除する (アクセス権の問題でトリッキーな消し方をする必要がある) $UserChoicePath = ("Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{0}" -f $Extension) if ($RegKey = [Registry]::CurrentUser.OpenSubKey(($UserChoicePath + '\UserChoice'), [RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::ChangePermissions)) { $Acl = $RegKey.GetAccessControl() $Acl.Access | Where-Object {$_.AccessControlType -eq 'Deny'} | ForEach-Object { [void]$Acl.RemoveAccessRule($_) } $RegKey.SetAccessControl($Acl) $RegKey.Close() } [Registry]::CurrentUser.DeleteSubKeyTree($UserChoicePath, $false); if ($PSCmdlet.ParameterSetName -eq 'Command') { $FileType = $Extension.TrimStart('.') + 'file' } # 拡張子とファイルタイプの紐付け $SetFileType = & cmd.exe /c ("assoc {0}={1}" -f $Extension, $FileType) if ($PSCmdlet.ParameterSetName -eq 'Command') { # ファイルタイプと実行コマンドの紐付け $SetCommand = & cmd.exe /c ("ftype {0}={1} 2>null" -f $FileType, $Command.Replace('%', '^%')) # Powershellではなくコマンドラインの動作仕様に引きずられるので%を^%にエスケープする必要あり } if ($PSBoundParameters.ContainsKey('Icon')) { # ファイルアイコンの設定 $Key = ("HKLM:\SOFTWARE\Classes\{0}\DefaultIcon" -f $FileType) if (-not (Test-Path -LiteralPath $Key)) { New-Item -Path $Key -Force | Out-Null } $RegKey = [Registry]::LocalMachine.OpenSubKey(("SOFTWARE\Classes\{0}\DefaultIcon" -f $FileType), $true) if ($RegKey) { $RegKey.SetValue("", $Icon, [RegistryValueKind]::ExpandString) $RegKey.Close() } } #システムへの変更通知 Update-FileAssoc } # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// function Remove-FileAssoc { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Extension ) # ユーザ固有の関連付けを削除する (アクセス権の問題でトリッキーな消し方をする必要がある) $UserChoicePath = ("Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\{0}" -f $Extension) if ($RegKey = [Registry]::CurrentUser.OpenSubKey(($UserChoicePath + '\UserChoice'), [RegistryKeyPermissionCheck]::ReadWriteSubTree, [System.Security.AccessControl.RegistryRights]::ChangePermissions)) { $Acl = $RegKey.GetAccessControl() $Acl.Access | Where-Object {$_.AccessControlType -eq 'Deny'} | ForEach-Object { [void]$Acl.RemoveAccessRule($_) } $RegKey.SetAccessControl($Acl) $RegKey.Close() } [Registry]::CurrentUser.DeleteSubKeyTree($UserChoicePath, $false); # 関連付けを削除する(カレントユーザ) $Path1 = ('HKCU:\Software\Classes\{0}' -f $Extension) $Path2 = ('HKCU:\Software\Classes\{0}_auto_file' -f ($Extension.Replace('.', [string]::Empty))) if (Test-Path -LiteralPath $Path1) { Remove-Item -LiteralPath ('HKCU:\Software\Classes\{0}' -f $Extension) -Recurse -Force } if (Test-Path -LiteralPath $Path2) { Remove-Item -LiteralPath ('HKCU:\Software\Classes\{0}_auto_file' -f ($Extension.Replace('.', [string]::Empty))) -Recurse -Force } # 拡張子とファイルタイプの紐付けを外す Start-Process -FilePath 'cmd.exe' -ArgumentList '/c "assoc .csv="' -Wait -NoNewWindow #システムへの変更通知 Update-FileAssoc } # //////////////////////////////////////////////////////////////////////////////////////// # 拡張子登録変更を反映させるためのWin32APIコール # https://msdn.microsoft.com/ja-jp/library/windows/desktop/bb762118(v=vs.85).aspx # //////////////////////////////////////////////////////////////////////////////////////// function Update-FileAssoc { $CSharp = @' private const int SHCNE_ASSOCCHANGED = 0x08000000; [System.Runtime.InteropServices.DllImport("Shell32.dll")] private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); public static void AssocReflesh() { SHChangeNotify(SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero); } '@ Add-Type -MemberDefinition $CSharp -Namespace WinAPI -Name Shell [WinAPI.Shell]::AssocReflesh() } # //////////////////////////////////////////////////////////////////////////////////////// # //////////////////////////////////////////////////////////////////////////////////////// Export-ModuleMember -Function *-TargetResource |