New Windows Module: win_file_compression (#56475)

* add win_compact module

* fixed line endings

* fix documentation

* Use cim method instead of wmi method

* renamed to win_file_compression
added single file support
added force option to avoid traversing large directory structures

* fixed end of file

* fixed renaming.
bench test still had win_compact as a module

* Removed more NTFS references and slight test tweaks
pull/65273/head
Micah Hunsberger 5 years ago committed by Jordan Borean
parent 7084dd727a
commit 19063166dd

@ -0,0 +1,118 @@
#!powershell
# Copyright: (c) 2019, Micah Hunsberger
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
Set-StrictMode -Version 2
$spec = @{
options = @{
path = @{ type = 'path'; required = $true }
state = @{ type = 'str'; default = 'present'; choices = 'absent', 'present' }
recurse = @{ type = 'bool'; default = $false }
force = @{ type = 'bool'; default = $true }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$path = $module.Params.path
$state = $module.Params.state
$recurse = $module.Params.recurse
$force = $module.Params.force
$module.Result.rc = 0
if(-not (Test-Path -LiteralPath $path)) {
$module.FailJson("Path to item, $path, does not exist.")
}
$item = Get-Item -LiteralPath $path -Force # Use -Force for hidden files
if (-not $item.PSIsContainer -and $recurse) {
$module.Warn("The recurse option has no effect when path is not a folder.")
}
$cim_params = @{
ClassName = 'Win32_LogicalDisk'
Filter = "DeviceId='$($item.PSDrive.Name):'"
Property = @('FileSystem', 'SupportsFileBasedCompression')
}
$drive_info = Get-CimInstance @cim_params
if ($drive_info.SupportsFileBasedCompression -eq $false) {
$module.FailJson("Path, $path, is not on a filesystemi '$($drive_info.FileSystem)' that supports file based compression.")
}
function Get-ReturnCodeMessage {
param(
[int]$code
)
switch ($code) {
0 { return "The request was successful." }
2 { return "Access was denied." }
8 { return "An unspecified failure occurred." }
9 { return "The name specified was not valid." }
10 { return "The object specified already exists." }
11 { return "The file system is not NTFS." }
12 { return "The platform is not Windows." }
13 { return "The drive is not the same." }
14 { return "The directory is not empty." }
15 { return "There has been a sharing violation." }
16 { return "The start file specified was not valid." }
17 { return "A privilege required for the operation is not held." }
21 { return "A parameter specified is not valid." }
}
}
function Get-EscapedFileName {
param(
[string]$FullName
)
return $FullName.Replace("\","\\").Replace("'","\'")
}
$is_compressed = ($item.Attributes -band [System.IO.FileAttributes]::Compressed) -eq [System.IO.FileAttributes]::Compressed
$needs_changed = $is_compressed -ne ($state -eq 'present')
if($force -and $recurse -and $item.PSIsContainer) {
if (-not $needs_changed) {
# Check the subfolders and files
$entries_to_check = $item.EnumerateFileSystemInfos("*", [System.IO.SearchOption]::AllDirectories)
foreach ($entry in $entries_to_check) {
$is_compressed = ($entry.Attributes -band [System.IO.FileAttributes]::Compressed) -eq [System.IO.FileAttributes]::Compressed
if ($is_compressed -ne ($state -eq 'present')) {
$needs_changed = $true
break
}
}
}
}
if($needs_changed) {
$module.Result.changed = $true
if ($item.PSIsContainer) {
$cim_obj = Get-CimInstance -ClassName 'Win32_Directory' -Filter "Name='$(Get-EscapedFileName -FullName $item.FullName)'"
} else {
$cim_obj = Get-CimInstance -ClassName 'CIM_LogicalFile' -Filter "Name='$(Get-EscapedFileName -FullName $item.FullName)'"
}
if($state -eq 'present') {
if(-not $module.CheckMode) {
$ret = Invoke-CimMethod -InputObject $cim_obj -MethodName 'CompressEx' -Arguments @{ Recursive = $recurse }
$module.Result.rc = $ret.ReturnValue
}
} else {
if(-not $module.CheckMode) {
$ret = $ret = Invoke-CimMethod -InputObject $cim_obj -MethodName 'UnCompressEx' -Arguments @{ Recursive = $recurse }
$module.Result.rc = $ret.ReturnValue
}
}
}
$module.Result.msg = Get-ReturnCodeMessage -code $module.Result.rc
if($module.Result.rc -ne 0) {
$module.FailJson($module.Result.msg)
}
$module.ExitJson()

@ -0,0 +1,100 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Micah Hunsberger (@mhunsber)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_file_compression
version_added: '2.10'
short_description: Alters the compression of files and directories on NTFS partitions.
description:
- This module sets the compressed attribute for files and directories on a filesystem that supports it like NTFS.
- NTFS compression can be used to save disk space.
options:
path:
description:
- The full path of the file or directory to modify.
- The path must exist on file system that supports compression like NTFS.
required: yes
type: path
state:
description:
- Set to C(present) to ensure the I(path) is compressed.
- Set to C(absent) to ensure the I(path) is not compressed.
type: str
choices:
- absent
- present
default: present
recurse:
description:
- Whether to recursively apply changes to all subdirectories and files.
- This option only has an effect when I(path) is a directory.
- When set to C(false), only applies changes to I(path).
- When set to C(true), applies changes to I(path) and all subdirectories and files.
type: bool
default: false
force:
description:
- This option only has an effect when I(recurse) is C(true)
- If C(true), will check the compressed state of all subdirectories and files
and make a change if any are different from I(compressed).
- If C(false), will only make a change if the compressed state of I(path) is different from I(compressed).
- If the folder structure is complex or contains a lot of files, it is recommended to set this
option to C(false) so that not every file has to be checked.
type: bool
default: true
author:
- Micah Hunsberger (@mhunsber)
notes:
- C(win_file_compression) sets the file system's compression state, it does not create a zip archive file.
- For more about NTFS Compression, see U(http://www.ntfs.com/ntfs-compressed.htm)
'''
EXAMPLES = r'''
- name: Compress log files directory
win_file_compression:
path: C:\Logs
state: present
- name: Decompress log files directory
win_file_compression:
path: C:\Logs
state: absent
- name: Compress reports directory and all subdirectories
win_file_compression:
path: C:\business\reports
state: present
recurse: yes
# This will only check C:\business\reports for the compressed state
# If C:\business\reports is compressed, it will not make a change
# even if one of the child items is uncompressed
- name: Compress reports directory and all subdirectories (quick)
win_file_compression:
path: C:\business\reports
compressed: yes
recurse: yes
force: no
'''
RETURN = r'''
rc:
description:
- The return code of the compress/uncompress operation.
- If no changes are made or the operation is successful, rc is 0.
returned: always
sample: 0
type: int
'''

@ -0,0 +1,5 @@
test_win_file_compression_suffix: win_file_compression .ÅÑŚÌβŁÈ [$!@^&test(;)]
test_win_file_compression_sub_directories:
- 'a'
- 'b'
test_win_file_compression_filename: 'foo.bar'

@ -0,0 +1,2 @@
dependencies:
- setup_remote_tmp_dir

@ -0,0 +1,224 @@
---
- name: set fact of special testing dir
set_fact:
test_directory: '{{ remote_tmp_dir }}\{{ test_win_file_compression_suffix }}'
- name: create sub directories
win_file:
state: directory
path: "{{ test_directory }}\\{{ item }}"
loop: "{{ test_win_file_compression_sub_directories }}"
- name: set main directory as hidden to test out edge cases
win_shell: (Get-Item -LiteralPath '{{ test_directory }}').Attributes = [System.IO.FileAttributes]::Hidden
- name: Compress parent directory
win_file_compression:
path: "{{ test_directory }}"
state: present
register: result
- name: Get actual attributes for parent directory
win_stat:
path: "{{ test_directory }}"
register: folder_info
- assert:
that:
- "'Compressed' in folder_info.stat.attributes"
- "result.changed == true"
- name: Get actual attributes for sub directories
win_stat:
path: "{{ test_directory }}\\{{ item }}"
register: subfolder_info
loop: "{{ test_win_file_compression_sub_directories }}"
- assert:
that:
- "'Compressed' not in item.stat.attributes"
loop: "{{ subfolder_info.results }}"
- name: Compress parent directory (idempotent)
win_file_compression:
path: "{{ test_directory }}"
state: present
register: result
- assert:
that:
- "result.changed == false"
- name: Compress parent directory and all subdirectories
win_file_compression:
path: "{{ test_directory }}"
state: present
recurse: yes
register: result
- name: Get actual attributes for parent directory
win_stat:
path: "{{ test_directory }}"
register: folder_info
- assert:
that:
- "'Compressed' in folder_info.stat.attributes"
- "result.changed == true"
- name: Get actual attributes for sub directories
win_stat:
path: "{{ test_directory }}\\{{ item }}"
register: subfolder_info
loop: "{{ test_win_file_compression_sub_directories }}"
- assert:
that:
- "'Compressed' in item.stat.attributes"
loop: "{{ subfolder_info.results }}"
- name: Compress parent directory and all subdirectories (idempotent)
win_file_compression:
path: "{{ test_directory }}"
state: present
recurse: yes
register: result
- assert:
that:
- "result.changed == false"
- name: Uncompress parent directory
win_file_compression:
path: "{{ test_directory }}"
state: absent
recurse: no
register: result
- name: Get actual attributes for parent directory
win_stat:
path: "{{ test_directory }}"
register: folder_info
- assert:
that:
- "'Compressed' not in folder_info.stat.attributes"
- "result.changed == true"
- name: Get actual attributes for sub directories
win_stat:
path: "{{ test_directory }}\\{{ item }}"
register: subfolder_info
loop: "{{ test_win_file_compression_sub_directories }}"
- assert:
that:
- "'Compressed' in item.stat.attributes"
loop: "{{ subfolder_info.results }}"
- name: Uncompress parent directory (idempotent)
win_file_compression:
path: "{{ test_directory }}"
state: absent
recurse: no
register: result
- assert:
that:
- "result.changed == false"
- name: Uncompress parent directory and all subdirectories
win_file_compression:
path: "{{ test_directory }}"
state: absent
recurse: yes
register: result
- name: Get actual attributes for parent directory
win_stat:
path: "{{ test_directory }}"
register: folder_info
- assert:
that:
- "'Compressed' not in folder_info.stat.attributes"
- "result.changed == true"
- name: Get actual attributes for sub directories
win_stat:
path: "{{ test_directory }}\\{{ item }}"
register: subfolder_info
loop: "{{ test_win_file_compression_sub_directories }}"
- assert:
that:
- "'Compressed' not in item.stat.attributes"
loop: "{{ subfolder_info.results }}"
- name: Uncompress parent directory and all subdirectories (idempotent)
win_file_compression:
path: "{{ test_directory }}"
state: absent
recurse: yes
register: result
- assert:
that:
- "result.changed == false"
- name: Create test file
win_file:
state: touch
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
- name: Compress specific file
win_file_compression:
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
state: present
register: result
- name: Get actual attributes of file
win_stat:
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
register: testfile_info
- assert:
that:
- "result.changed == true"
- "'Compressed' in testfile_info.stat.attributes"
- name: Compress specific file (idempotent)
win_file_compression:
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
state: present
register: result
- assert:
that:
- "result.changed == false"
- name: Uncompress specific file
win_file_compression:
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
state: absent
register: result
- name: Get actual attributes of file
win_stat:
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
register: testfile_info
- assert:
that:
- "result.changed == true"
- "'Compressed' not in testfile_info.stat.attributes"
- name: Uncompress specific file (idempotent)
win_file_compression:
path: "{{ test_directory }}\\{{ test_win_file_compression_filename }}"
state: absent
register: result
- assert:
that:
- "result.changed == false"
Loading…
Cancel
Save