首先需要 pip install wmi,psutil,pypiwin32,tkinter 其中的pypiwin32引入时间会比较长等待一下
这个版本是一年半之前的,缺点是当扫描大磁盘时会超出‘list’的长度,导致程序出错,所以目前只能够扫描文件较少的磁盘并进行恢复。使用python3可以运行就是会出现乱码, python2的情况某些会因为中文会报错提示‘utf8’不能编码,需要修改
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __Author__: 前世是只狼
import os
import wmi
import platform
import psutil
import tkinter
import binascii
import tkinter.messagebox # 消息框
from tkinter import ttk # 导入内部包
win = tkinter.Tk() # 初始化
win.title("NTFS数据恢复")
win.geometry('450x320') # 设置窗口大小
win.resizable(False, False)#固定窗体
titelRecoed = tkinter.Label(win, text=" NTFS数据恢复 ", bg="#ff4400", font=("宋体", 14), width=20, height=1).grid(row=0, column=1)
listForTitel = tkinter.Label(win, text="请选择要恢复的分区:", bg="#ff4400", font=("宋体", 10), width=25, height=1).grid(row=1, column=0)
allDisk = psutil.disk_partitions() # 获得所有的磁盘信息
allDiskForList = []
allDiskForChoose = []
for sdiskpart in allDisk:
if sdiskpart.fstype == 'NTFS': # 判定是否是NTFS格式
disk = sdiskpart.device
diskForList = disk
temp = psutil.disk_usage(disk) # 获得磁盘的详细信息
total = round(temp.total / 1073741824, 2) # 字节数转换成GB
totalForList = (total, "GB")
allDiskForNtfs = (diskForList, totalForList)
allDiskForList.append(allDiskForNtfs)
allDiskForChoose.append(diskForList)
# 双击事件
def onDBClick(event):
askForonDBClick = tkinter.messagebox.askyesno('提示', '确定恢复该文件吗?')
if askForonDBClick:
index = tree.index(tree.selection()[0])
disks = r"\\.\%s" % (diskList.get()[0:2]) # 打印选中的值
filename = 'D:\\deletefile\\' + get_name(disks)[index].replace("\x00", "")
with open(filename, 'wb') as f:
f.write(binascii.a2b_hex(get_content(disks)[index]))
f.close()
else:
pass
Version1 = platform.platform()
c = wmi.WMI()
for sys in c.Win32_OperatingSystem():
Version = sys.Caption.encode("UTF8") # 操作系统
Vernum = sys.BuildNumber # 版本号
num = sys.OSArchitecture.encode("UTF8") # 操作系统位数
Processesnum = sys.NumberOfProcesses # 进程总数
for processor in c.Win32_Processor():
CPU = processor.Name.strip().encode("UTF8") # cpu型号
for Memory in c.Win32_PhysicalMemory():
Memory = (int(Memory.Capacity) / 1048576) # 内存大小
disklist = [] # 数组
for disk in c.Win32_LogicalDisk(DriveType=3):
disklist.append(disk.Caption+"%0.2f%% free" % (100.0 * int(disk.FreeSpace) / int(disk.Size)))
tree = ttk.Treeview(win) # 创建树状列表for删除文件
tree1 = ttk.Treeview(win, show="headings")
tree1["columns"] = ("参数", "内容")
tree1.column("参数", width=55) # 表示列,不显示
tree1.column("内容", width=120) # 表示列,不显示
tree1.heading("参数", text="参数") # 显示表头
tree1.heading("内容", text="内容") # 显示表头
tree["selectmode"] = "browse" # 按啥都只能同时高亮一行;
tree.bind("", onDBClick) #Double
tree.grid(row=3, column=1)
tree1.insert("", 0, text="操作系统", values=("操作系统", Version1[0: 10])) # 插入数据,
tree1.insert("", 1, text="版本号", values=("版本号", Vernum))
tree1.insert("", 2, text="操作系统", values=("操作系统", "%s" % num))
tree1.insert("", 3, text="进程总数", values=("进程总数", Processesnum))
tree1.insert("", 4, text="CPU型号", values=("CPU型号", CPU))
tree1.insert("", 5, text="内存大小", values=("内存大小", "%sMB" % Memory))
for disks in disklist:
tree1.insert("", 6, text="磁盘剩余", values=("磁盘剩余", disks))
tree1.grid(row=3, column=0)
chooseDisk = "" # 扫描的磁盘首先确定
def Hexchange(data): # 转换成大写16进制的自定义函数
disk_boot = [] # 用来存放$Boot的数组
boot = []
for i, c in enumerate(data):
disk_boot.append(c)
for key, value in enumerate(disk_boot):
if key % 2 == 0:
boot.append(disk_boot[key] + disk_boot[key + 1])
return boot
def Odisk(disk_choose, start, offset_disk): # 输出要绝对读写的磁盘盘符,开始位置和偏移量
with open(disk_choose, 'rb+') as f:
# 以文件起始位置作为相对位置,偏移0个长度
f.seek(start, 0)
partdata = f.read(offset_disk).hex().upper()
return partdata
def get_content(disk): # 恢复文件
partdata = Odisk(disk, 0, 512)
disk_boot = Hexchange(partdata) # 用来存放$Boot的数组
disk_bpb = disk_boot[11:84] # 获取BPB区:数组存放
sector_bytes = disk_bpb[0:2] # 扇区字节数
sector_bytes.reverse()
cluster_sector = disk_bpb[2] # 每个簇有几个扇区
cluster_sector_data = str(cluster_sector)
track_sector = disk_bpb[13:15] # 每磁道的扇区数
track_sector.reverse()
track_num = disk_bpb[29:37] # 扇区总数
track_num.reverse()
disk_mft = disk_bpb[37:45] # $MFT的起始簇号
disk_mft.reverse()
mft_track = disk_bpb[53:57] # 每个MFT记录的簇数
mft_track.reverse()
indexes_track = disk_bpb[57:61] # 索引的簇数
indexes_track.reverse()
offset = int(cluster_sector_data, 16) * int(mft_str(disk_mft), 16) * int(mft_str(sector_bytes), 16) # $MFT位置(偏移量) := $MFT的逻辑簇号 *每簇扇区数 *每扇区字节数
mftdata = Odisk(disk, offset, 256 * 1024) # 存放MFT
d_mft = Hexchange(mftdata)
partition_point_array = [] # 尝试把没有被删除的文件给复制出来
for index, item in enumerate(d_mft):
condition1 = ['46', '49', '4C', '45']
condition2 = ['00', '00'] # 文件类型,文件是把01变成00/ // 0已删除 1正常文件 2已删除目录 3目录正使用
if d_mft[index:index + 4] == condition1 and d_mft[index + 22:index + 24] == condition2:
partition_point = index # 分割点
partition_point_array.append(partition_point)
delete_content_name_array = [] # 文件名数组,内容数组
for ipar in range(len(partition_point_array)):
structure_size = d_mft[partition_point_array[ipar] + 24:partition_point_array[ipar] + 28]
structure_size.reverse()
mft_record = d_mft[ # 属性 10,30是常驻属性;80是非常驻属性
partition_point_array[ipar] + 0:partition_point_array[ipar] + int(mft_str(structure_size), 16)] # 第一个属性偏移量
attribute_offset = mft_record[20:22]
attribute_offset.reverse()
length_10 = mft_record[int(mft_str(attribute_offset), 16) + 4:int(mft_str(attribute_offset), 16) + 8]
length_10.reverse()
start_80 = 0
attribute_80 = ['80', '00', '00', '00']
attribute_length_80 = [] # 80属性的属性长度
for index, item in enumerate(mft_record):
if mft_record[index:index + 4] == attribute_80:
start_80 = index
attribute_length_80 = mft_record[start_80 + 4:start_80 + 8]
attribute_length_80.reverse() # 存放80属性
end = int(mft_str(attribute_length_80), 16)
mft_80 = mft_record[start_80:start_80 + end]
attribute_sign = mft_80[8]
if attribute_sign == "00":
content = mft_80[24:len(mft_80)] # 被删除的内容数组
delete_content_name = ''
for icn in range(len(content)):
delete_content_name += str(content[icn])
delete_content_name_array.append(delete_content_name)
else:
content = mft_80[24:len(mft_80)]
data_function = content[len(content) - 8:len(content)]
temp = data_function[0]
temp_a = str(temp)[0]
temp_b = str(temp)[1]
temp_c = int(temp_a) + int(temp_b)
dataruns_cont = data_function[1:temp_c + 1]
dataruns_top = dataruns_cont[int(temp_b):int(temp_a) + 1]
dataruns_top.reverse()
dataruns_bigen = int(mft_str(dataruns_top), 16) * 8 * 512
dataruns_size = dataruns_cont[0:int(temp_b)]
dataruns_size.reverse()
dataruns_zijie = int(mft_str(dataruns_size), 16) * 8 * 512
dataruns_temp = Odisk(disk, dataruns_bigen,dataruns_zijie)
dataruns = Hexchange(dataruns_temp)
delete_content_name = ''
for icn in range(len(dataruns)):
delete_content_name += str(dataruns[icn])
delete_content_name_array.append(delete_content_name)
return delete_content_name_array
def get_name(disk): # 获取被删除文件名数组
partdata = Odisk(disk, 0, 512)
disk_boot = Hexchange(partdata) # 用来存放$Boot的数组
disk_bpb = disk_boot[11:84] # 获取BPB区:数组存放
sector_bytes = disk_bpb[0:2] # 扇区字节数
sector_bytes.reverse()
cluster_sector = disk_bpb[2] # 每个簇有几个扇区
cluster_sector_data = str(cluster_sector)
track_sector = disk_bpb[13:15] # 每磁道的扇区数
track_sector.reverse()
track_num = disk_bpb[29:37] # 扇区总数
track_num.reverse()
disk_mft = disk_bpb[37:45] # $MFT的起始簇号
disk_mft.reverse()
mft_track = disk_bpb[53:57] # 每个MFT记录的簇数
mft_track.reverse()
indexes_track = disk_bpb[57:61] # 索引的簇数
indexes_track.reverse()
offset = int(cluster_sector_data, 16) * int(mft_str(disk_mft), 16) * int(mft_str(sector_bytes), 16) # $MFT位置(偏移量) := $MFT的逻辑簇号 *每簇扇区数 *每扇区字节数
mftdata = Odisk(disk, offset, 256 * 1024) # 存放MFT
d_mft = Hexchange(mftdata)
partition_point_array = [] # 尝试把没有被删除的文件给复制出来
for index, item in enumerate(d_mft):
condition1 = ['46', '49', '4C', '45']
condition2 = ['00', '00'] # 文件类型,文件是把01变成00/ // 0已删除 1正常文件 2已删除目录 3目录正使用
if d_mft[index:index + 4] == condition1 and d_mft[index + 22:index + 24] == condition2:
partition_point = index # 分割点
partition_point_array.append(partition_point)
delete_file_name_array = []
delete_content_name_array = []
for ipar in range(len(partition_point_array)):
structure_size = d_mft[partition_point_array[ipar] + 24:partition_point_array[ipar] + 28]
structure_size.reverse()
mft_record = d_mft[ # 属性 10,30是常驻属性;80是非常驻属性
partition_point_array[ipar] + 0:partition_point_array[ipar] + int(mft_str(structure_size), 16)] # 第一个属性偏移量
attribute_offset = mft_record[20:22]
attribute_offset.reverse()
length_10 = mft_record[int(mft_str(attribute_offset), 16) + 4:int(mft_str(attribute_offset), 16) + 8]
length_10.reverse()
delete_file_name = mft_record[int(mft_str(attribute_offset), 16) + int(mft_str(length_10), 16) + 90:int(mft_str(attribute_offset),16) + int(mft_str(length_10), 16) + 112]
delete_file_name_data = ""
for ij in range(len(delete_file_name)):
delete_file_name_data += chr(int(delete_file_name[ij], 16))
delete_file_name_array.append(delete_file_name_data)
start_80 = 0
attribute_80 = ['80', '00', '00', '00']
attribute_length_80 = [] # 80属性的属性长度
for index, item in enumerate(mft_record):
if mft_record[index:index + 4] == attribute_80:
start_80 = index
attribute_length_80 = mft_record[start_80 + 4:start_80 + 8]
attribute_length_80.reverse() # 存放80属性
end = int(mft_str(attribute_length_80), 16)
mft_80 = mft_record[start_80:start_80 + end]
content = mft_80[24:len(mft_80)] # 被删除的内容数组
delete_content_name = ''
for icn in range(len(content)):
delete_content_name += chr(int(content[icn], 16))
delete_content_name_array.append(delete_content_name)
return delete_file_name_array
def scan(*args): # 处理事件,*args表示可变参数
askForUser = tkinter.messagebox.askyesno('提示', '确定扫描该磁盘吗?')
if askForUser:
disks = r"\\.\%s" % (diskList.get()[0:2])
for index, item in enumerate(get_name(disks)):
myid = tree.insert("", index, text=item.replace("\x00", ""), values=index) # ""表示父节点是根
else:
pass
def allRecovery(*args): # 处理事件,*args表示可变参数
askForallRecovery = tkinter.messagebox.askyesno('提示', '确定全部恢复吗?')
if askForallRecovery:
disks = r"\\.\%s" % (diskList.get()[0:2]) # 打印选中的值
for index, item in enumerate(get_name(disks)):
filename = 'D:\\deletefile\\' + item.replace("\x00", "")
with open(filename, 'wb') as f:
f.write(binascii.a2b_hex(get_content(disks)[index]))
f.close()
else:
pass
def recovery(*args): # 处理事件,*args表示可变参数
askForUserRec = tkinter.messagebox.askyesno('提示', '确定恢复吗?')
if askForUserRec:
tkinter.messagebox.askyesno('提示', '双击恢复')
else:
pass
# 转化成字符串自定义函数:输入倒叙后的数组;返回字符串
def mft_str(mft_array):
mft_array_data = ''
for mft_len in range(len(mft_array)):
mft_array_data += str(mft_array[mft_len])
return mft_array_data
# 创建保存恢复出来文件的文件夹
def mkdir(path):
folder = os.path.exists(path)
if not folder: # 判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs(path) # makedirs 创建文件时如果路径不存在会创建这个路径
tkinter.messagebox.showwarning('提示', 'D:\deletefile创建成功!')
else:
tkinter.messagebox.showwarning('提示', 'D:\deletefile存在!')
mkdir("D:\\deletefile") # 默认保存文件夹
diskList = tkinter.StringVar()
diskListChosen = ttk.Combobox(win, width=26, textvariable=diskList)
diskListChosen['values'] = allDiskForChoose
diskListChosen.current(0)
diskListChosen.grid(row=1, column=1)
listButten = tkinter.Button(win, text="启动扫描", command=scan).grid(row=1, column=2) # 绑定事件,点击按钮时时,绑定scan()函数)
listButten1 = tkinter.Button(win, text="双击恢复", command=recovery).grid(row=3, column=2)
listicon1 = tkinter.Label(win, width=5).grid(row=1, column=5)
listButten2 = tkinter.Button(win, text="全部恢复", command=allRecovery).grid(row=2, column=2)
listiconleft2 = tkinter.Label(win, text="已删除文件", width=28, height=1, bg="#ff4400").grid(row=2, column=1)
listiconleft3 = tkinter.Label(win, text="电脑系统信息", width=25, height=1, bg="#ff4400").grid(row=2, column=0)
win.mainloop() # 进入消息循环
因为换了电脑最后出来的效果如下图:
之前的效果如下: