首先需要 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("<Double-1>", onDBClick) #<Button-1>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() # 進入消息循環
因爲換了電腦最後出來的效果如下圖:
之前的效果如下: