windows-tools-powershell 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- windows_tools/powershell/__init__.py +328 -0
- windows_tools_powershell-0.5.0-py3.12-nspkg.pth +1 -0
- windows_tools_powershell-0.5.0.dist-info/LICENSE +29 -0
- windows_tools_powershell-0.5.0.dist-info/METADATA +300 -0
- windows_tools_powershell-0.5.0.dist-info/RECORD +8 -0
- windows_tools_powershell-0.5.0.dist-info/WHEEL +5 -0
- windows_tools_powershell-0.5.0.dist-info/namespace_packages.txt +1 -0
- windows_tools_powershell-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# This file is part of windows_tools module
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
PowerShellRunner is a class that allows to run powershell scripts / commands without hassle
|
|
8
|
+
Also allows auto script elevation
|
|
9
|
+
|
|
10
|
+
Versioning semantics:
|
|
11
|
+
Major version: backward compatibility breaking changes
|
|
12
|
+
Minor version: New functionality
|
|
13
|
+
Patch version: Backwards compatible bug fixes
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__intname__ = "windows_tools.powershell"
|
|
18
|
+
__author__ = "Orsiris de Jong"
|
|
19
|
+
__copyright__ = "Copyright (C) 2019-2026 Orsiris de Jong"
|
|
20
|
+
__description__ = "PowerShell interpreter wrapper"
|
|
21
|
+
__licence__ = "BSD 3 Clause"
|
|
22
|
+
__version__ = "0.5.0"
|
|
23
|
+
__build__ = "2026032001"
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
from logging import getLogger
|
|
27
|
+
import tempfile
|
|
28
|
+
|
|
29
|
+
from command_runner import command_runner
|
|
30
|
+
from ofunctions.json_sanitize import json_sanitize
|
|
31
|
+
from ofunctions.random import random_string
|
|
32
|
+
import windows_tools.registry
|
|
33
|
+
|
|
34
|
+
logger = getLogger()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_POWERSHELL_ELEVATOR_SCRIPT = r"""
|
|
38
|
+
# Run elevated commands and get stdout/stderr and (partial) exitcode
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
# Function blatantly stolen from https://stackoverflow.com/a/15669365/2635443
|
|
42
|
+
function Write-StdErr {
|
|
43
|
+
param ([PSObject] $InputObject)
|
|
44
|
+
$outFunc = if ($Host.Name -eq 'ConsoleHost') {
|
|
45
|
+
[Console]::Error.WriteLine
|
|
46
|
+
} else {
|
|
47
|
+
$host.ui.WriteErrorLine
|
|
48
|
+
}
|
|
49
|
+
if ($InputObject) {
|
|
50
|
+
[void] $outFunc.Invoke($InputObject.ToString())
|
|
51
|
+
} else {
|
|
52
|
+
[string[]] $lines = @()
|
|
53
|
+
$Input | % { $lines += $_.ToString() }
|
|
54
|
+
[void] $outFunc.Invoke($lines -join "`r`n")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Parts blatanly stolen from https://stackoverflow.com/a/60216595/2635443
|
|
59
|
+
|
|
60
|
+
# Get the ID and security principal of the current user account
|
|
61
|
+
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
|
|
62
|
+
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
|
|
63
|
+
|
|
64
|
+
# Get the security principal for the Administrator role
|
|
65
|
+
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
|
|
66
|
+
|
|
67
|
+
# Check to see if we are currently running "as Administrator"
|
|
68
|
+
if ($myWindowsPrincipal.IsInRole($adminRole))
|
|
69
|
+
{
|
|
70
|
+
#Update window to show we're running as administrator
|
|
71
|
+
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
|
|
72
|
+
$Host.UI.RawUI.BackgroundColor = "DarkBlue"
|
|
73
|
+
clear-host
|
|
74
|
+
$Host.UI.Write("Running elevated NPBackup task processing")
|
|
75
|
+
} else {
|
|
76
|
+
$capture_stdout = New-TemporaryFile
|
|
77
|
+
$capture_stderr = New-TemporaryFile
|
|
78
|
+
# Relaunch our processs as admin
|
|
79
|
+
# This will disconnect fds, so we need to redirect stdout and stderr in order to catch them
|
|
80
|
+
$processParams = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
|
|
81
|
+
|
|
82
|
+
# Specify the current script path and name as a parameter
|
|
83
|
+
$processParams.Arguments = $myInvocation.MyCommand.Definition;
|
|
84
|
+
$processParams.Arguments += " > $capture_stdout 2> $capture_stderr"
|
|
85
|
+
|
|
86
|
+
# Indicate that the process should be elevated
|
|
87
|
+
$processParams.Verb = "RunAs"
|
|
88
|
+
# We might hide our window, or leave this commented out so options above apply
|
|
89
|
+
$processParams.WindowStyle = "Hidden"
|
|
90
|
+
|
|
91
|
+
# Start the new process
|
|
92
|
+
try {
|
|
93
|
+
$process = [System.Diagnostics.Process]::Start($processParams)
|
|
94
|
+
While (-not $process.HasExited) {
|
|
95
|
+
Start-Sleep -Milliseconds 100
|
|
96
|
+
}
|
|
97
|
+
# Arbitrary wait time for fds to close
|
|
98
|
+
Start-Sleep -Milliseconds 500
|
|
99
|
+
Write-Host (Get-Content -Path $capture_stdout)
|
|
100
|
+
Write-StdErr (Get-Content -Path $capture_stderr)
|
|
101
|
+
# This will only return 0 or 1, other integers are transformed into 1
|
|
102
|
+
exit($process.ExitCode)
|
|
103
|
+
} catch {
|
|
104
|
+
Write-output "Process cannot be launched as admin. Trying as standard user"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# The following code will run elevated
|
|
110
|
+
___CODE_PLACEHOLDER___
|
|
111
|
+
} catch {
|
|
112
|
+
Write-Host "PS Error: $($_.Exception.Message)"*
|
|
113
|
+
exit(1)
|
|
114
|
+
}
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class PowerShellRunner:
|
|
119
|
+
"""
|
|
120
|
+
Identify powershell interpreter and allow running scripts / commands with ExecutionPolicy ByPass
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, powershell_interpreter=None):
|
|
124
|
+
self.powershell_interpreter = powershell_interpreter
|
|
125
|
+
|
|
126
|
+
if powershell_interpreter is not None and os.path.isfile(
|
|
127
|
+
powershell_interpreter
|
|
128
|
+
):
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
# Try to guess powershell path if no valid path given
|
|
132
|
+
interpreter_executable = "powershell.exe"
|
|
133
|
+
for syspath in ["sysnative", "system32"]:
|
|
134
|
+
try:
|
|
135
|
+
# Let's try native powershell (64 bit) first or else
|
|
136
|
+
# Import-Module may fail when running 32 bit powershell on 64 bit arch
|
|
137
|
+
best_guess = os.path.join(
|
|
138
|
+
os.environ.get("SYSTEMROOT", "C:"),
|
|
139
|
+
syspath,
|
|
140
|
+
"WindowsPowerShell",
|
|
141
|
+
"v1.0",
|
|
142
|
+
interpreter_executable,
|
|
143
|
+
)
|
|
144
|
+
if os.path.isfile(best_guess):
|
|
145
|
+
self.powershell_interpreter = best_guess
|
|
146
|
+
break
|
|
147
|
+
except KeyError:
|
|
148
|
+
pass
|
|
149
|
+
if self.powershell_interpreter is None:
|
|
150
|
+
try:
|
|
151
|
+
ps_paths = os.path.dirname(os.environ["PSModulePath"]).split(";")
|
|
152
|
+
for ps_path in ps_paths:
|
|
153
|
+
if ps_path.endswith("Modules"):
|
|
154
|
+
ps_path = ps_path.strip("Modules")
|
|
155
|
+
possible_ps_path = os.path.join(ps_path, interpreter_executable)
|
|
156
|
+
if os.path.isfile(possible_ps_path):
|
|
157
|
+
self.powershell_interpreter = possible_ps_path
|
|
158
|
+
break
|
|
159
|
+
except KeyError:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
if self.powershell_interpreter is None:
|
|
163
|
+
raise OSError("Could not find any valid powershell interpreter")
|
|
164
|
+
|
|
165
|
+
self.major_version = None
|
|
166
|
+
self.minor_version = None
|
|
167
|
+
self.major_version, self.minor_version = self.get_version()
|
|
168
|
+
|
|
169
|
+
def get_version(self):
|
|
170
|
+
"""
|
|
171
|
+
Get major / minor version as tuple
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
if self.powershell_interpreter is None:
|
|
175
|
+
return 0, 0
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
exit_code, output = self.run_command("$PSVersionTable.PSVersion.ToString()")
|
|
179
|
+
if exit_code != 0:
|
|
180
|
+
# If the above method does not work, let's try registry method
|
|
181
|
+
try:
|
|
182
|
+
output = windows_tools.registry.get_value(
|
|
183
|
+
hive=windows_tools.registry.HKEY_LOCAL_MACHINE,
|
|
184
|
+
key=r"SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine",
|
|
185
|
+
value="PowerShellVersion",
|
|
186
|
+
)
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
output = "-1.-1"
|
|
189
|
+
try:
|
|
190
|
+
# output = major_version.minor_version.build.revision for newer powershells
|
|
191
|
+
major_version, minor_version, _, _ = output.split(".")
|
|
192
|
+
except (ValueError, TypeError):
|
|
193
|
+
# output = major_version.minor_version for some powershells (v3.0)
|
|
194
|
+
major_version, minor_version = output.split(".")
|
|
195
|
+
return int(major_version), int(minor_version)
|
|
196
|
+
except (ValueError, TypeError):
|
|
197
|
+
return -1, 0
|
|
198
|
+
|
|
199
|
+
def run_command(
|
|
200
|
+
self,
|
|
201
|
+
command,
|
|
202
|
+
timeout=None,
|
|
203
|
+
valid_exit_codes=[0],
|
|
204
|
+
encoding=None,
|
|
205
|
+
force_utf8=True,
|
|
206
|
+
to_json=False,
|
|
207
|
+
json_depth=2,
|
|
208
|
+
sanitize_json=True,
|
|
209
|
+
**kwargs,
|
|
210
|
+
):
|
|
211
|
+
"""
|
|
212
|
+
Accepts subprocess.check_output arguments
|
|
213
|
+
Accepts command_runner arguments like timeout, encoding and valid_exit_codes
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
if self.powershell_interpreter is None:
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
# So older powershell versions used unicode_escape, newer ones are utf-8
|
|
220
|
+
# AFAIK, the change happened between powershell 4 and 5
|
|
221
|
+
if not encoding and self.major_version is not None:
|
|
222
|
+
if self.major_version >= 5:
|
|
223
|
+
encoding = "utf-8"
|
|
224
|
+
else:
|
|
225
|
+
encoding = "unicode_escape"
|
|
226
|
+
|
|
227
|
+
utf8_prefix = ""
|
|
228
|
+
if force_utf8:
|
|
229
|
+
utf8_prefix = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
|
|
230
|
+
to_json_suffix = ""
|
|
231
|
+
if to_json:
|
|
232
|
+
to_json_suffix = "| ConvertTo-Json -Depth {}".format(json_depth)
|
|
233
|
+
|
|
234
|
+
# Do not add -NoProfile so we don't end up in a path we're not supposed to
|
|
235
|
+
command = (
|
|
236
|
+
self.powershell_interpreter
|
|
237
|
+
+ " -NonInteractive -NoLogo {}{}{}".format(
|
|
238
|
+
utf8_prefix, command, to_json_suffix
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
logger.debug("Running powershell command:\n%s", command)
|
|
242
|
+
|
|
243
|
+
exit_code, output = command_runner(
|
|
244
|
+
command,
|
|
245
|
+
timeout=timeout,
|
|
246
|
+
valid_exit_codes=valid_exit_codes,
|
|
247
|
+
encoding=encoding,
|
|
248
|
+
**kwargs,
|
|
249
|
+
)
|
|
250
|
+
if sanitize_json:
|
|
251
|
+
return exit_code, json_sanitize(output)
|
|
252
|
+
return exit_code, output
|
|
253
|
+
|
|
254
|
+
def run_script(
|
|
255
|
+
self,
|
|
256
|
+
script,
|
|
257
|
+
*args,
|
|
258
|
+
timeout=None,
|
|
259
|
+
valid_exit_codes=[0],
|
|
260
|
+
encoding=None,
|
|
261
|
+
elevated=False,
|
|
262
|
+
**kwargs,
|
|
263
|
+
):
|
|
264
|
+
"""
|
|
265
|
+
Accepts subprocess.check_output arguments
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
if self.powershell_interpreter is None:
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
if not encoding and self.major_version is not None:
|
|
272
|
+
if self.major_version >= 5:
|
|
273
|
+
encoding = "utf-8"
|
|
274
|
+
else:
|
|
275
|
+
encoding = "unicode_escape"
|
|
276
|
+
|
|
277
|
+
if elevated:
|
|
278
|
+
try:
|
|
279
|
+
# We need to prepare a temporary script that will be injected into the elevator script
|
|
280
|
+
if script.endswith(".ps1") and os.path.isfile(script):
|
|
281
|
+
with open(script, "r", encoding=encoding) as fp:
|
|
282
|
+
script_content = fp.read()
|
|
283
|
+
powershell_elevator_script = _POWERSHELL_ELEVATOR_SCRIPT.replace(
|
|
284
|
+
"___CODE_PLACEHOLDER___", script_content
|
|
285
|
+
)
|
|
286
|
+
else:
|
|
287
|
+
# Assume we are given an inline script instead of a file
|
|
288
|
+
powershell_elevator_script = _POWERSHELL_ELEVATOR_SCRIPT.replace(
|
|
289
|
+
"___CODE_PLACEHOLDER___", script
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# Write a temporary elevator script with the content of the given script
|
|
293
|
+
powershell_elevator_temp_script = os.path.join(
|
|
294
|
+
tempfile.gettempdir(), f"{__intname__}_{random_string(8)}.ps1"
|
|
295
|
+
)
|
|
296
|
+
with open(
|
|
297
|
+
powershell_elevator_temp_script, "w", encoding=encoding
|
|
298
|
+
) as fp:
|
|
299
|
+
fp.write(powershell_elevator_script)
|
|
300
|
+
script = powershell_elevator_temp_script
|
|
301
|
+
except OSError as exc:
|
|
302
|
+
logger.error("Could not prepare powershell elevator: {}".format(exc))
|
|
303
|
+
return 1, "Could not prepare powershell elevator: {}".format(exc)
|
|
304
|
+
|
|
305
|
+
# Welcome in Powershell hell where running a script with -Command argument returns exit
|
|
306
|
+
# codes 0 or 1 whereas as running with -File argument returns your script exit code
|
|
307
|
+
command = (
|
|
308
|
+
self.powershell_interpreter
|
|
309
|
+
+ " -executionPolicy Bypass -NonInteractive -NoLogo -NoProfile -File "
|
|
310
|
+
+ script
|
|
311
|
+
+ (" " if len(args) > 0 else " ")
|
|
312
|
+
+ " ".join('"' + arg + '"' for arg in args)
|
|
313
|
+
)
|
|
314
|
+
logger.debug("Running powershell command:\n%s", command)
|
|
315
|
+
exit_code, output = command_runner(
|
|
316
|
+
command,
|
|
317
|
+
timeout=timeout,
|
|
318
|
+
valid_exit_codes=valid_exit_codes,
|
|
319
|
+
encoding=encoding,
|
|
320
|
+
**kwargs,
|
|
321
|
+
)
|
|
322
|
+
try:
|
|
323
|
+
os.remove(powershell_elevator_temp_script)
|
|
324
|
+
except Exception as exc:
|
|
325
|
+
logger.debug(
|
|
326
|
+
"Could not remove temporary powershell elevator script: {}".format(exc)
|
|
327
|
+
)
|
|
328
|
+
return exit_code, output
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import sys, types, os;p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('windows_tools',));importlib = __import__('importlib.util');__import__('importlib.machinery');m = sys.modules.setdefault('windows_tools', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('windows_tools', [os.path.dirname(p)])));m = m or sys.modules.setdefault('windows_tools', types.ModuleType('windows_tools'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019-2024, netinvent, Orsiris de Jong, contact@netinvent.fr
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: windows_tools_powershell
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: PowerShell interpreter wrapper
|
|
5
|
+
Home-page: https://github.com/netinvent/windows_tools
|
|
6
|
+
Author: NetInvent - Orsiris de Jong
|
|
7
|
+
Author-email: contact@netinvent.fr
|
|
8
|
+
Keywords: wmi,virtualization,file,acl,ntfs,refs,antivirus,security,firewall,office
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development
|
|
12
|
+
Classifier: Topic :: System
|
|
13
|
+
Classifier: Topic :: System :: Operating System
|
|
14
|
+
Classifier: Topic :: System :: Shells
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
19
|
+
Classifier: Operating System :: Microsoft
|
|
20
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
21
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
22
|
+
Requires-Python: >=3.5
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: command-runner >=1.2.1
|
|
26
|
+
Requires-Dist: windows-tools.registry >=1.0.1
|
|
27
|
+
Requires-Dist: ofunctions.json-sanitize >0.1.1
|
|
28
|
+
|
|
29
|
+
# windows_tools
|
|
30
|
+
## Collection of useful python functions around Microsoft Windows
|
|
31
|
+
|
|
32
|
+
[](https://opensource.org/licenses/BSD-3-Clause)
|
|
33
|
+
[](http://isitmaintained.com/project/netinvent/ofunctions "Percentage of issues still open")
|
|
34
|
+
[](https://codeclimate.com/github/netinvent/windows_tools/maintainability)
|
|
35
|
+
[](https://codecov.io/gh/netinvent/windows_tools)
|
|
36
|
+
[](https://github.com/netinvent/windows_tools/actions/workflows/windows.yaml)
|
|
37
|
+
[](https://github.com/netinvent/windows_tools/releases/latest)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
windows_tools is a set of various recurrent functions amongst
|
|
41
|
+
|
|
42
|
+
- antivirus: antivirus state and list of installed AV engines
|
|
43
|
+
- bitlocker: drive encryption status and protector key retrieval
|
|
44
|
+
- bitness: simple bitness identification
|
|
45
|
+
- file_utils: file ownership handling, NTFS & ReFS ACL handling, file listing with permission fixes
|
|
46
|
+
- impersonate: python Runas implementation
|
|
47
|
+
- installed_software: list of installed software from registry, 32 and 64 bits
|
|
48
|
+
- logical_disk: logical disk listing
|
|
49
|
+
- misc: basic time related functions to convert windows ticks into epoch / date strings
|
|
50
|
+
- office: microsoft Office version identification, works for click & run, O365 and legacy
|
|
51
|
+
- powershell: powershell wrapper to identify interpreter and run scripts or commands
|
|
52
|
+
- product_key: windows product key retrieval
|
|
53
|
+
- registry: registry 32 and 64 bit API
|
|
54
|
+
- securityprivilege: enable / disable various security privileges for user
|
|
55
|
+
- server: windows server identification
|
|
56
|
+
- signtool: Easily sign executables with Authenticode
|
|
57
|
+
- updates: get all installed windows updates based on COM, WMI and registry retrieval methods
|
|
58
|
+
- users: user lookup for SID/PySID/username
|
|
59
|
+
- virtualization: virtualization platform identification for guest
|
|
60
|
+
- windows_firewall: windows firewall state retrieval
|
|
61
|
+
- wmi_queries: windows WMI query wrapper, wmi timezone converters
|
|
62
|
+
|
|
63
|
+
It is compatible with Python 3.5+ and is tested on Windows only (obviously).
|
|
64
|
+
|
|
65
|
+
## Setup
|
|
66
|
+
|
|
67
|
+
You may install the whole `windows_tools` package or any subpackage using the following commands
|
|
68
|
+
```
|
|
69
|
+
pip install windows_tools
|
|
70
|
+
pip install windows_tools.<subpackage>
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### antivirus
|
|
77
|
+
|
|
78
|
+
The antivirus package tries to list installed Antivirus products via the SecurityCenter API (using WMI calls).
|
|
79
|
+
Since SecurityCenter API does not exist on Windows Servers, we also need to check for installed antivirus software using the uninstall registry keys.
|
|
80
|
+
These checks are more fuzzy, but allow to detect the following products:
|
|
81
|
+
|
|
82
|
+
- avast
|
|
83
|
+
- avira
|
|
84
|
+
- avg technologies
|
|
85
|
+
- bitdefender
|
|
86
|
+
- dr web
|
|
87
|
+
- eset
|
|
88
|
+
- f-secure
|
|
89
|
+
- g data software
|
|
90
|
+
- kaspersky
|
|
91
|
+
- mcafee
|
|
92
|
+
- panda security
|
|
93
|
+
- sophos
|
|
94
|
+
- trend micro
|
|
95
|
+
- malwarebytes
|
|
96
|
+
- vipre
|
|
97
|
+
- sentinel one
|
|
98
|
+
- cybereason
|
|
99
|
+
- cylance
|
|
100
|
+
|
|
101
|
+
On top of that list, it will detect any installed software containing "antivirus/antiviral/antimalware" in the name.
|
|
102
|
+
|
|
103
|
+
Please report back if your antivirus is not detected, so we can improve the fuzzy detection here.
|
|
104
|
+
|
|
105
|
+
Usage
|
|
106
|
+
```
|
|
107
|
+
import windows_tools.antivirus
|
|
108
|
+
|
|
109
|
+
result = windows_tools.antivirus.get_installed_antivirus_software()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`result` will contain a list of dict like
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
[{
|
|
116
|
+
'name': 'Windows Defender',
|
|
117
|
+
'version': None,
|
|
118
|
+
'publisher': None,
|
|
119
|
+
'enabled': False,
|
|
120
|
+
'is_up_to_date': True,
|
|
121
|
+
'type': 'Windows Defender / Security Essentials'
|
|
122
|
+
}, {
|
|
123
|
+
'name': 'Malwarebytes version 4.4.6.132',
|
|
124
|
+
'version': '4.4.6.132',
|
|
125
|
+
'publisher': 'Malwarebytes',
|
|
126
|
+
'enabled': None,
|
|
127
|
+
'is_up_to_date': None,
|
|
128
|
+
'type': None
|
|
129
|
+
}
|
|
130
|
+
]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Warning**
|
|
134
|
+
Keys `enabled`, `is_up_to_date` and `type` are only filled via securityCenter API*.
|
|
135
|
+
Keys `version` and `publisher` are only filled via installed software list.
|
|
136
|
+
The only guaranteed filled key will always be `name`
|
|
137
|
+
|
|
138
|
+
### bitlocker
|
|
139
|
+
|
|
140
|
+
Bitlocker can only work on NTFS or ReFS formatted disks.
|
|
141
|
+
Bitlocker keys can only be retrieved on local disks.
|
|
142
|
+
|
|
143
|
+
#### Usage
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
import windows_tools.bitlocker
|
|
147
|
+
|
|
148
|
+
result = windows_tools.bitlocker.get_bitlocker_full_status()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`result` will contain a dict as follows containing raw strings from `manage-bde` windows tool:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
{
|
|
155
|
+
'C:': {
|
|
156
|
+
'status': 'Chiffrement de lecteur BitLocker\xa0: outil de configuration version 10.0.19041\nCopyright (C) 2013 Microsoft Corporation. Tous droits réservés.\n\nVolume C: [Windows ]\n[Volume du système d?exploitation]\n\n Taille : 855,14 Go\n Version de BitLocker : Aucun\n État de la conversion : Intégralement déchiffré\n Pourcentage chiffré : 0,0%\n Méthode de chiffrement : Aucun\n État de la protection\xa0: Protection désactivée\n État du verrouillage : Déverrouillé\n Champ d?identification : Aucun\n Protecteurs de clés : Aucun trouvé\n\n',
|
|
157
|
+
'protectors': None
|
|
158
|
+
},
|
|
159
|
+
'D:': {
|
|
160
|
+
'status': 'Chiffrement de lecteur BitLocker\xa0: outil de configuration version 10.0.19041\nCopyright (C) 2013 Microsoft Corporation. Tous droits réservés.\n\nVolume D: [Étiquette inconnue]\n[Volume de données]\n\n Taille : Inconnu Go\n Version de BitLocker : 2.0\n État de la conversion : Inconnu\n Pourcentage chiffré : Inconnu%\n Méthode de chiffrement : XTS-AES 128\n État de la protection\xa0: Inconnu\n État du verrouillage : Verrouillé\n Champ d?identification : Inconnu\n Déverrouillage automatique : Désactivé\n Protecteurs de clés\xa0:\n Password\n Mot de passe numérique\n\n',
|
|
161
|
+
'protectors': 'Chiffrement de lecteur BitLocker\xa0: outil de configuration version 10.0.19041\nCopyright (C) 2013 Microsoft Corporation. Tous droits réservés.\n\nVolume D: [Étiquette inconnue]\nTous les protecteurs de clés\n\n Password :\n ID : {SOMEPASS-WORD-ICAN-NNOT-REMEMBERWELL}\n\n Mot de passe numérique :\n ID : {SOMEPASS-GUID-ICAN-NNOT-REMEMBERWELL}\n\n'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
You may parse those or simply pretty print since print will not interpret special characters from a dict or multiple variables at once:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
result = windows_tools.bitlocker.get_bitlocker_full_status()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
result = get_bitlocker_full_status()
|
|
173
|
+
for drive in result:
|
|
174
|
+
for designation, content in result[drive].items():
|
|
175
|
+
print(designation, content)
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Warning** bitlocker needs to be run as admin.
|
|
180
|
+
Running as non administrator will produce the following logs
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
Don't have permission to get bitlocker drive status for C:.
|
|
184
|
+
Don't have permission to get bitlocker drive protectors for C:.
|
|
185
|
+
Don't have permission to get bitlocker drive status for D:.
|
|
186
|
+
Don't have permission to get bitlocker drive protectors for D:.
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Output shall be
|
|
190
|
+
```
|
|
191
|
+
{
|
|
192
|
+
'C:': {
|
|
193
|
+
'status': None,
|
|
194
|
+
'protectors': None
|
|
195
|
+
},
|
|
196
|
+
'D:': {
|
|
197
|
+
'status': None,
|
|
198
|
+
'protectors': None
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
You can check that you have administrator rights with `windows_utils.users` module
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
### bitness
|
|
207
|
+
|
|
208
|
+
### file_utils
|
|
209
|
+
|
|
210
|
+
### impersonate
|
|
211
|
+
|
|
212
|
+
### installed_software
|
|
213
|
+
|
|
214
|
+
### logical_disk
|
|
215
|
+
|
|
216
|
+
### misc
|
|
217
|
+
|
|
218
|
+
### office
|
|
219
|
+
|
|
220
|
+
### powershell
|
|
221
|
+
|
|
222
|
+
### product_key
|
|
223
|
+
|
|
224
|
+
### registry
|
|
225
|
+
|
|
226
|
+
### securityprivilege
|
|
227
|
+
|
|
228
|
+
### server
|
|
229
|
+
|
|
230
|
+
### signtool
|
|
231
|
+
|
|
232
|
+
signtool is designed to make the windows executable signature as simple as possible.
|
|
233
|
+
Once the Windows SDK is installed on your machine, you can sign any executable with the following commands:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
from windows_tools.signtool import SignTool
|
|
237
|
+
signer = SignTool()
|
|
238
|
+
signer.sign(r"c:\path\to\executable", bitness=64)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Note that current versions of `signtool.exe` that come with Windows 10 SDK automagically detect hardware EV certificate tokens like Safenet.
|
|
242
|
+
|
|
243
|
+
When using former certificate files in order to sign an executable, one should use the following syntax:
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
from windows_tools.signtool import SignTool
|
|
247
|
+
signer = SignTool(certificate=r"c:\path\to\cert.pfx", pkcs12_password="the_certificate_file_password")
|
|
248
|
+
signer.sign(r"c:\path\to\executable", bitness=64)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
If the wrong certificate is used to sign, please open `certmgr.msc`, go to Private > Certificates and remove the certificate you don't want.
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
### updates
|
|
255
|
+
|
|
256
|
+
Windows updates can be retrieved via a COM object that talks to Windows Update service, via WMI requests or via registry entries.
|
|
257
|
+
All methods can return different results, so they are combined into one function.
|
|
258
|
+
|
|
259
|
+
Usage
|
|
260
|
+
```
|
|
261
|
+
import windows_tools.updates
|
|
262
|
+
|
|
263
|
+
result = windows_tools.updates.get_windows_updates(filter_duplicates=True, include_all_states=False)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`result` will contain a list of dict like
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
[{
|
|
270
|
+
'kb': 'KB123456',
|
|
271
|
+
'date': '2021-01-01 00:01:02',
|
|
272
|
+
'title': 'Some update title',
|
|
273
|
+
'description': 'Some update description',
|
|
274
|
+
'supporturl': 'https://support.microsoft.com/someID',
|
|
275
|
+
'operation': 'Installation'
|
|
276
|
+
'result': 'Installed'
|
|
277
|
+
}, {
|
|
278
|
+
'kb': None,
|
|
279
|
+
'date': '2021-01-01 00:01:02',
|
|
280
|
+
'title': 'Windows 10 20H1 update',
|
|
281
|
+
'description': 'Pretty big system update',
|
|
282
|
+
'supporturl': 'https://support.microsoft.com/someID',
|
|
283
|
+
'operation': 'Installation'
|
|
284
|
+
'result': 'Installed'
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Using `filter_duplicates` will avoid returning multiple times the same KB from different sources.
|
|
290
|
+
This setting is enabled by default.
|
|
291
|
+
|
|
292
|
+
The parameter `include_all_states` set to True will include all updates, even those who failed to install or are superseeded.
|
|
293
|
+
|
|
294
|
+
### users
|
|
295
|
+
|
|
296
|
+
### virtualization
|
|
297
|
+
|
|
298
|
+
### windows_firewall
|
|
299
|
+
|
|
300
|
+
### wmi_queries
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
windows_tools_powershell-0.5.0-py3.12-nspkg.pth,sha256=E9edsbJlPNsIwiHCaIkjTRDYgRikSM9HyFO_vVIGoYQ,503
|
|
2
|
+
windows_tools/powershell/__init__.py,sha256=GV4dAkKj7f7Das9u7x75dV0yAIXQ4icEEzTf1m3Ei9w,12566
|
|
3
|
+
windows_tools_powershell-0.5.0.dist-info/LICENSE,sha256=f0RzkBgdSPGnJwMnaqiHvlwV33AyQFKonbWVUXdOXEg,1590
|
|
4
|
+
windows_tools_powershell-0.5.0.dist-info/METADATA,sha256=dH_927esTWhHI3I2C5dwGZY7221EIh4Kv8ePEFq5XpM,11310
|
|
5
|
+
windows_tools_powershell-0.5.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
6
|
+
windows_tools_powershell-0.5.0.dist-info/namespace_packages.txt,sha256=UuWyvD5IaqqN0pyVLoOeAkChn8_q1b2qrZdS5pBMU0k,14
|
|
7
|
+
windows_tools_powershell-0.5.0.dist-info/top_level.txt,sha256=UuWyvD5IaqqN0pyVLoOeAkChn8_q1b2qrZdS5pBMU0k,14
|
|
8
|
+
windows_tools_powershell-0.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
windows_tools
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
windows_tools
|